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.


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.

Prevention and further reading

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++ and how to fix it

A critical buffer overflow in `create_hex_string()` within `hmlangw.cpp` let an unconditional 16-iteration loop write past the bounds of a 100-byte `hex` buffer using unchecked `sprintf` calls. The fix replaces `sprintf` with `snprintf` and caps the loop iterations based on the actual destination buffer size, closing off a memory corruption path reachable from serial or network input.

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation

medium

How Integer Overflow happens in C++ image processing and how to fix it

A signed integer overflow in OpenCV's `bilateralFilter.cpp` allowed the buffer size calculation `cal_width * cal_height * cn` to wrap around to a small or negative value, causing `padding.resize()` to allocate far less memory than needed. Subsequent `memcpy` operations would then write beyond the allocated buffer, creating a heap corruption primitive. The fix is a single targeted cast to `size_t` that promotes the multiplication to unsigned 64-bit arithmetic before any overflow can occur.

critical

How Stack Buffer Overflows Happen in C with sprintf() and How to Fix Them

A critical stack buffer overflow was discovered in `libuv/Learn-libuv/docs/code/tty-gravity/main.c` where `sprintf()` wrote ANSI escape sequences and user-controlled variables into a fixed 500-byte buffer without any bounds checking. An attacker controlling the `pos`, `width`, or `message` variables could overflow the stack, overwrite return addresses, and potentially achieve arbitrary code execution. The fix replaces `sprintf()` with `snprintf()` and adds explicit length validation to ensure wr

high

How insecure string copy functions happen in C and how to fix them

A high-severity buffer overflow risk was discovered in `login/main.c` where `strcpy()` was used to copy the `HOME` environment variable into a fixed-size 512-byte buffer without any bounds checking. An attacker controlling the `HOME` environment variable could overflow `pwd_file_name`, potentially corrupting memory or hijacking execution. The fix replaces the two-step `strcpy`/`strcat` pattern with a single, bounds-safe `snprintf` call.