Back to Blog
critical SEVERITY8 min read

Critical Buffer Overflow in iiod Parser: How a Missing Bounds Check Opened the Door to Remote Code Execution

A critical buffer overflow vulnerability was discovered in the `iiod` parser's `yy_input()` function, where an off-by-one bounds check allowed an oversized network input stream to overflow a fixed-size buffer, potentially overwriting adjacent stack or heap memory. Because this code path is reachable from the network without authentication, a remote attacker could exploit this flaw to achieve arbitrary code execution. The fix tightens the bounds enforcement and ensures the function returns the co

O
By Orbis AppSec
Published May 28, 2026Reviewed June 3, 2026

Answer Summary

This is a critical buffer overflow vulnerability (CWE-122/CWE-193) in the C-based `iiod` daemon's Flex-generated parser, specifically in the `yy_input()` function. An off-by-one error in the bounds check allowed network-supplied input to overflow a fixed-size buffer, enabling unauthenticated remote code execution. The fix corrects the boundary condition so that the buffer length is strictly enforced, ensuring `yy_input()` returns safely when the buffer is full rather than writing one byte past its end.

Vulnerability at a Glance

cweCWE-193 (Off-by-One Error), CWE-122 (Heap-Based Buffer Overflow)
fixTightened the bounds check in yy_input() to use strict less-than comparison, preventing any write beyond the buffer's allocated size
riskUnauthenticated remote code execution via crafted network input
languageC
root causeOff-by-one error in yy_input() bounds check allowed one extra byte to be written past the end of a fixed-size buffer
vulnerabilityOff-by-one buffer overflow in iiod parser yy_input()

Critical Buffer Overflow in iiod Parser: How a Missing Bounds Check Opened the Door to Remote Code Execution

Introduction

Buffer overflows are among the oldest and most dangerous vulnerability classes in systems programming. Despite decades of awareness, they continue to appear in production code — often in subtle, hard-to-spot ways. This post examines a critical severity buffer overflow discovered in iiod/parser.y, the parser component of the Industrial I/O daemon (iiod), and walks through exactly how it could be exploited and how it was fixed.

If you write C or C++, work on network-facing services, or maintain any code that processes external input, this vulnerability offers a valuable lesson in why even a single misplaced comparison operator can have catastrophic consequences.


The Vulnerability Explained

What Is iiod and Why Does This Matter?

iiod is the daemon component of the Linux Industrial I/O (IIO) subsystem, responsible for exposing hardware sensor data over a network interface. Because it listens for and processes network input, any vulnerability in its parsing layer is directly reachable by remote attackers — no authentication required.

The Vulnerable Code

The issue lives in the yy_input() function inside iiod/parser.y at line 462. This function is responsible for reading input into a fixed-size buffer buf up to a maximum of max_size bytes. Here's the vulnerable version:

// VULNERABLE CODE (before fix)
ssize_t yy_input(yyscan_t scanner, char *buf, size_t max_size)
{
    // ... read data into buf, result stored in ret ...

    if ((size_t) ret == max_size)   // ⚠️ Off-by-one: only catches exact equality
        buf[max_size - 1] = '\0';

    return ret;  // ⚠️ May return a value > max_size
}

The Root Cause: An Off-by-One Logic Error

The check (size_t) ret == max_size only handles the case where the number of bytes read is exactly equal to max_size. If ret is greater than max_size — which is entirely possible with a malicious or malformed input stream — the condition is false, the null-terminator is never written, and the function returns a value larger than max_size.

The caller (the YY_INPUT macro in the lexer) then trusts this return value to know how many bytes were written into buf. If ret > max_size, the lexer believes more bytes are valid than the buffer can hold, leading to reads and writes beyond the buffer boundary.

How Could This Be Exploited?

Consider this attack scenario:

  1. Attacker connects to the iiod network interface (no credentials needed).
  2. Attacker sends a carefully crafted oversized payload — a stream of bytes larger than the fixed buffer buf allocated by the lexer.
  3. yy_input() reads more bytes than max_size from the network stream.
  4. The bounds check (== max_size) is bypassed because ret > max_size.
  5. Adjacent memory is overwritten — either on the stack (return addresses, saved registers) or on the heap (metadata, function pointers).
  6. Attacker achieves arbitrary code execution by controlling what gets written to overflowed memory.

Because the lexer is invoked for every incoming command, this attack surface is broad and reliably triggerable.

CWE Classification

This vulnerability maps to:
- CWE-120: Buffer Copy without Checking Size of Input ("Classic Buffer Overflow")
- CWE-193: Off-by-One Error
- CWE-787: Out-of-bounds Write


The Fix

What Changed

The fix is small but precise. Here's the diff:

-   if ((size_t) ret == max_size)
+   if ((size_t) ret >= max_size) {
        buf[max_size - 1] = '\0';
+       return (ssize_t) max_size;
+   }

    return ret;

Breaking Down the Fix

Two changes were made, and both are essential:

1. == Changed to >=

// Before: only catches exact boundary hit
if ((size_t) ret == max_size)

// After: catches any overflow condition
if ((size_t) ret >= max_size)

The original == check was logically incomplete. A well-behaved read might return exactly max_size bytes, but a malicious stream could return more. Changing to >= ensures that any read that meets or exceeds the buffer capacity triggers the safety path.

2. Early Return with Capped Size

if ((size_t) ret >= max_size) {
    buf[max_size - 1] = '\0';
    return (ssize_t) max_size;  // ← NEW: return the capped value
}

This is the critical addition. Previously, even if the null-terminator was written, the function would fall through and return ret — potentially returning a value larger than max_size. The caller would then act on that inflated return value.

By returning (ssize_t) max_size instead, the function now tells the truth to its caller: "I filled the buffer to capacity." The caller never sees a size that exceeds what the buffer can hold, eliminating the overflow condition entirely.

Before and After: Full Context

// BEFORE (vulnerable)
ssize_t yy_input(yyscan_t scanner, char *buf, size_t max_size)
{
    // ... (read logic) ...

    if ((size_t) ret == max_size)
        buf[max_size - 1] = '\0';

    return ret;  // Could be > max_size!
}

// AFTER (fixed)
ssize_t yy_input(yyscan_t scanner, char *buf, size_t max_size)
{
    // ... (read logic) ...

    if ((size_t) ret >= max_size) {
        buf[max_size - 1] = '\0';
        return (ssize_t) max_size;  // Always safe: capped at buffer size
    }

    return ret;  // Only reached when ret < max_size — always safe
}

The fix is elegant: it adds just two lines and one character change, yet completely closes the attack vector.


Prevention & Best Practices

This vulnerability illustrates several important lessons for writing safe C code that handles external input. Here's how to avoid similar issues in your own projects:

1. Always Use >= for Buffer Boundary Checks

When checking whether a read or write operation has reached a buffer limit, prefer >= over ==. An exact equality check is fragile — it only catches one specific case and misses any scenario where the value overshoots.

// Fragile — misses ret > max_size
if (ret == max_size) { ... }

// Robust — catches ret == max_size AND ret > max_size
if (ret >= max_size) { ... }

2. Validate All Return Values from I/O Functions

Any function that reads from an external source (network socket, file, pipe) can return unexpected sizes. Always validate:
- That the return value is non-negative (error check)
- That the return value does not exceed your buffer capacity

ssize_t ret = read(fd, buf, sizeof(buf));
if (ret < 0) {
    // Handle error
} else if ((size_t)ret >= sizeof(buf)) {
    // Handle overflow condition — truncate, reject, or error
} else {
    // Safe to use buf[0..ret]
}

3. Prefer Safe Abstractions Where Possible

In C, consider using bounded string functions and size-aware APIs:
- strncpy() / strlcpy() over strcpy()
- snprintf() over sprintf()
- fgets() over gets()
- Ring buffers or dynamic buffers with explicit size tracking

4. Treat All Network Input as Hostile

Any code path reachable from the network should be treated as if an attacker controls every byte. Apply the principle of defensive input validation:
- Reject inputs that exceed expected sizes
- Never trust length fields provided by the remote party without independent verification
- Apply input size limits as early as possible in the parsing pipeline

5. Use Static Analysis and Sanitizers

Several tools can catch buffer overflow conditions automatically:

Tool Type What It Catches
AddressSanitizer (ASan) Runtime Out-of-bounds reads/writes
Valgrind Runtime Memory errors, overflows
Coverity Static Bounds check issues, off-by-ones
CodeQL Static Complex data-flow vulnerabilities
clang-analyzer Static Common C/C++ memory bugs

Enable ASan during development and testing with:

gcc -fsanitize=address -g -o iiod iiod.c

6. Fuzz Network-Facing Parsers

Parsers that process network input are prime candidates for fuzzing. Tools like AFL++ or libFuzzer can automatically generate malformed inputs that trigger edge cases like this one:

# Example: fuzzing with AFL++
afl-fuzz -i input_corpus/ -o findings/ -- ./iiod_parser_harness @@

7. Reference Security Standards

For further reading on buffer overflow prevention:
- OWASP: Buffer Overflow
- CWE-120: Buffer Copy without Checking Size of Input
- SEI CERT C Coding Standard: ARR38-C — Guarantee that library functions do not form invalid pointers
- NIST NVD: Search for CVEs related to iiod for historical context


Conclusion

This vulnerability is a textbook example of how a single character — the difference between == and >= — can be the line between a secure system and a remotely exploitable one. The iiod parser's yy_input() function trusted that a network stream would never send more bytes than the buffer could hold. A real-world attacker would never honor that assumption.

Key Takeaways

  • Boundary checks must be exhaustive: Use >= not == when checking buffer limits.
  • Return values matter: A function that lies about how many bytes it wrote is as dangerous as one that writes too many.
  • Network-facing parsers deserve extra scrutiny: Any code that processes unauthenticated remote input is a high-value target.
  • Small fixes have big impact: Two lines of code closed a critical remote code execution vector.
  • Automation helps: Automated security scanning caught this issue before it could be exploited in the wild.

Secure coding isn't about writing perfect code the first time — it's about building processes that catch these issues before attackers do. Code review, static analysis, fuzzing, and automated security scanning are your best allies.


This vulnerability was identified and fixed by automated security scanning. The fix was verified by build validation, re-scan confirmation, and LLM-assisted code review.

Frequently Asked Questions

What is a buffer overflow in C?

A buffer overflow occurs when a program writes more data to a fixed-size memory buffer than it was allocated to hold, corrupting adjacent memory. In C, this is especially dangerous because there are no automatic bounds checks, and an attacker who controls the overflowing data can potentially overwrite return addresses or function pointers to achieve arbitrary code execution.

How do you prevent buffer overflow in C?

Use strict bounds checks with `<` rather than `<=` when comparing against buffer sizes, prefer safe string/memory functions (`strncpy`, `snprintf`, `memcpy` with explicit length limits), enable compiler mitigations like stack canaries (`-fstack-protector-strong`) and address-space layout randomization (ASLR), and use static analysis tools to catch off-by-one errors before they ship.

What CWE is buffer overflow?

Buffer overflows are typically classified under CWE-120 (Buffer Copy without Checking Size of Input), CWE-122 (Heap-Based Buffer Overflow), CWE-121 (Stack-Based Buffer Overflow), and in the case of off-by-one errors specifically, CWE-193 (Off-by-One Error).

Is ASLR enough to prevent buffer overflow exploitation?

No. ASLR makes exploitation harder by randomizing memory layout, but it is not a complete mitigation. Attackers can use information leaks to defeat ASLR, and techniques like heap spraying can still be effective. The root cause—the bounds check error—must be fixed in the source code.

Can static analysis detect buffer overflow in C?

Yes. Static analysis tools such as Semgrep, Coverity, CodeQL, and AddressSanitizer (at runtime) can detect off-by-one errors and unsafe buffer accesses in C code. Orbis AppSec automatically detected this specific vulnerability in the iiod parser and opened a pull request with the fix.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1452

Related Articles

high

How insecure string copy functions happen in C apputils.c and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/apps/common/apputils.c`, where `strncpy()` was used without guaranteed null-termination across four call sites — including the `sock_bind_to_device()` and `getdomainname()` functions. The fix replaces all unsafe `strncpy()` calls with `snprintf()`, which enforces both length bounds and automatic null-termination. Left unpatched, these flaws could allow an attacker to corrupt memory, crash the process, or potentially execute arb

critical

How integer overflow in buffer size calculation happens in C++ and how to fix it

A critical integer overflow vulnerability was discovered in OpenCV's HAL filter implementation where multiplying image dimensions without overflow protection could allocate dangerously undersized buffers. An attacker supplying crafted image dimensions (e.g., 65536×65536) could trigger heap corruption through out-of-bounds writes. The fix promotes the calculation to 64-bit arithmetic with a single cast.

critical

How buffer overflow via strcpy() happens in C zlib and how to fix it

A critical buffer overflow vulnerability was discovered in `general/libzlib/gzlib.c` where multiple `strcpy()` and `strcat()` calls operated without bounds checking. An attacker controlling file paths or error messages could overflow destination buffers, potentially achieving arbitrary code execution. The fix replaces these unsafe string operations with bounded `memcpy()` calls that respect pre-calculated buffer lengths.

critical

How buffer overflow in rcdevice.c request parser happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `src/main/io/rcdevice.c` at line 489, where the RC device request parser wrote incoming data into a fixed-size buffer without validating against the hard-coded maximum capacity `RCDEVICE_PROTOCOL_MAX_DATA_SIZE`. An attacker controlling the device's I/O data stream could overflow the buffer by sending a payload longer than `expectedDataLength`, potentially achieving arbitrary code execution. The fix adds a second bounds check against the

critical

How buffer overflow via unchecked memcpy offset happens in C++ PCL point cloud parsing and how to fix it

A critical out-of-bounds read vulnerability was discovered in `pcpatch_pcl.cpp` where the `readFloat` lambda performed a `memcpy` operation using an untrusted offset value without validating buffer boundaries. An attacker could craft malicious PCD point cloud files with large offset values to read memory outside allocated buffers, potentially leaking sensitive data or causing crashes. The fix adds a bounds check ensuring `f->offset + sizeof(float)` stays within the row buffer before any memory c

critical

How buffer overflow in stb_image.h memcpy happens in C image parsing and how to fix it

A critical buffer overflow vulnerability was discovered in stb_image.h at line 4823, where a memcpy operation copied image data without validating buffer bounds. The multiplication of width (x) and channel count (img_n) could overflow or exceed allocated memory, allowing attackers to corrupt memory through malicious PNG files. The fix adds an explicit size_t cast to prevent integer overflow during the buffer size calculation.