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

critical

How buffer overflow happens in C tar header parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `microtar/microtar.c` where the `raw_to_header()` and `header_to_raw()` functions used unbounded `strcpy()` and `sprintf()` calls to copy tar header fields. Malicious tar files with non-null-terminated name fields could overflow destination buffers, potentially leading to code execution. The fix replaces all unsafe string operations with bounded alternatives: `memcpy()` with explicit null-termination and `snprintf()` instead of `sprintf(

critical

How buffer overflow happens in C ieee80211_input() and how to fix it

A critical buffer overflow vulnerability was discovered in `src/firmware/src/net/ieee80211.c` at line 1584, where the `ieee80211_input()` function processed raw 802.11 data frames without verifying that the incoming frame was large enough to contain a valid `ieee80211_frame` header. An attacker within wireless range could craft undersized or malformed frames to trigger memory corruption, potentially leading to remote code execution on the firmware. The fix adds a single, targeted bounds check th

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.

critical

How buffer overflow in FuzzIxml.c sprintf() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `fuzzer/FuzzIxml.c` where `sprintf()` wrote a PID-formatted filename into a fixed 256-byte stack buffer without any bounds checking. The fix replaces `sprintf()` with `snprintf()`, explicitly passing the buffer size to prevent any overflow. While exploitation in this specific fuzzer context requires local access, the pattern is a textbook example of CWE-120 that developers should recognize and eliminate everywhere it appears.

critical

How buffer overflow happens in C HTML parsing and how to fix it

A critical buffer overflow vulnerability in `include/html_parse.h` allowed attackers to overflow buffers by providing malicious HTML input exceeding buffer capacity. The fix adds proper bounds checking before memcpy() operations to prevent memory corruption and potential code execution.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.