How Buffer Overflow Happens in C ieee80211_input() and How to Fix It
Summary
A critical buffer overflow vulnerability was discovered in src/firmware/src/net/ieee80211.c, where the ieee80211_input() function processed raw 802.11 data frames without first 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 in the firmware — potentially enabling remote code execution. The fix is a single, targeted bounds check inserted before the vulnerable call to ieee80211_input_data().
Introduction
The src/firmware/src/net/ieee80211.c file sits at the heart of a device's wireless stack. It is responsible for receiving raw 802.11 frames off the air and routing them to the appropriate handler based on frame type: management, control, or data. That routing logic lives in ieee80211_input(), and until this fix, it had a dangerous blind spot.
When a data frame arrived (IEEE80211_FC0_TYPE_DATA), the function immediately forwarded it to ieee80211_input_data(frame, length, rssi) — no questions asked. There was no check that length was actually large enough to hold a struct ieee80211_frame header. Any code inside ieee80211_input_data() that reads fields from the frame header was doing so on the assumption that the frame was well-formed. An attacker who could inject a frame shorter than sizeof(struct ieee80211_frame) bytes could violate that assumption, causing the firmware to read (or write) memory it was never supposed to touch.
This matters to developers working on embedded networking code because the pattern is easy to introduce: you receive a uint8_t *frame with an accompanying uint32_t length, and it feels natural to trust that the caller has already validated the size. In a raw wireless receive path, no one has.
The Vulnerability Explained
The Vulnerable Code
Before the fix, the data-frame branch of ieee80211_input() looked like this (around line 1602–1606):
case IEEE80211_FC0_TYPE_DATA:
ieee80211_input_data(frame, length, rssi);
break;
The function signature for ieee80211_input is:
void ieee80211_input(uint8_t *frame, uint32_t length, int16_t rssi)
Both frame and length come directly from the wireless hardware receive buffer — they are fully attacker-controlled. The length field reflects whatever was encoded in the received frame, and there is no prior sanitization step before ieee80211_input() is called.
Why This Is Dangerous
ieee80211_input_data() will immediately begin parsing frame as a struct ieee80211_frame, accessing fields like the address fields (i_addr1, i_addr2, i_addr3), the sequence control field, and potentially QoS or HT control fields depending on the frame subtype. Each of those accesses is a pointer dereference into frame at a fixed offset.
If length is, say, 4 bytes — far smaller than sizeof(struct ieee80211_frame) (typically 24 bytes for a basic data frame) — then ieee80211_input_data() will read bytes beyond the end of the allocated frame buffer when it tries to access later fields. Depending on memory layout, this can:
- Leak sensitive data from adjacent memory regions (e.g., cryptographic keys, session state)
- Corrupt memory if any write operations follow the bad read
- Crash the firmware (denial of service) if the out-of-bounds access hits unmapped memory
- Enable code execution if an attacker can carefully control what lies beyond the buffer
Attack Scenario
An attacker within 802.11 radio range of the target device crafts a raw data frame with a valid 802.11 frame control field (FC0_TYPE_DATA) but a total length of only 6 bytes — just enough to pass the frame-type check, but far too short to contain a full ieee80211_frame header. They inject this frame using a monitor-mode wireless adapter and a tool like scapy or a custom kernel module.
The firmware's receive interrupt fires, calls ieee80211_input(frame, 6, rssi), enters the IEEE80211_FC0_TYPE_DATA case, and immediately calls ieee80211_input_data(frame, 6, rssi). Inside that function, code reads frame->i_addr2 at byte offset 10 — 4 bytes past the end of the 6-byte buffer. The attacker repeats this with varying lengths and frame content to probe memory layout, then crafts a payload that overwrites a function pointer or return address.
This attack requires no authentication, no association, and no prior knowledge of the device beyond its presence on the air.
The Fix
What Changed
A single line was added to the IEEE80211_FC0_TYPE_DATA case in ieee80211_input(), at line 1606:
case IEEE80211_FC0_TYPE_DATA:
+ if(length < sizeof(struct ieee80211_frame)) return;
ieee80211_input_data(frame, length, rssi);
break;
Before vs. After
Before (vulnerable):
case IEEE80211_FC0_TYPE_DATA:
ieee80211_input_data(frame, length, rssi);
break;
After (fixed):
case IEEE80211_FC0_TYPE_DATA:
if(length < sizeof(struct ieee80211_frame)) return;
ieee80211_input_data(frame, length, rssi);
break;
Why This Fix Works
sizeof(struct ieee80211_frame) is a compile-time constant that gives the exact number of bytes needed to represent a minimal, well-formed 802.11 frame header. By comparing length against this value before calling ieee80211_input_data(), the code guarantees that any downstream access to header fields within the first sizeof(struct ieee80211_frame) bytes is safe.
If the frame is too short, the function returns immediately — no memory is accessed, no state is corrupted, and the malformed frame is silently discarded. This is the correct behavior for a firmware receive path: drop what you cannot safely process.
The fix is minimal and targeted. It does not change the logic for well-formed frames, does not introduce new dependencies, and adds negligible overhead (a single integer comparison per data frame).
Note for maintainers: Similar length checks should be audited for the
IEEE80211_FC0_TYPE_MGTandIEEE80211_FC0_TYPE_CTLbranches. The fix addresses the data-frame path, but management and control frame handlers (ieee80211_input_ctl, and any management frame dispatcher) should be reviewed to confirm they perform equivalent minimum-length validation before header field access.
Prevention & Best Practices
1. Always Validate Frame/Packet Length Before Header Access
In any C code that processes binary protocol data, establish a pattern: check length before you dereference. This applies to every protocol layer — 802.11, Ethernet, IP, TCP, TLS record parsing, and custom binary protocols alike.
/* Pattern: always gate header access with a length check */
if (length < sizeof(struct my_protocol_header)) {
return -EINVAL; /* or return; for void functions */
}
struct my_protocol_header *hdr = (struct my_protocol_header *)buf;
/* Now safe to access hdr->fields */
2. Use sizeof() of the Target Structure, Not Magic Numbers
Hardcoded size constants (e.g., if (length < 24)) are fragile — they break silently if the structure definition changes. Using sizeof(struct ieee80211_frame) ties the check directly to the structure and will automatically update if the struct is modified.
3. Check Incrementally for Variable-Length Headers
802.11 frames have variable-length headers (QoS data adds 2 bytes, HT control adds 4 more, etc.). After the initial sizeof(struct ieee80211_frame) check, add secondary checks before accessing optional fields:
if (length < sizeof(struct ieee80211_frame)) return;
struct ieee80211_frame *wh = (struct ieee80211_frame *)frame;
if (IEEE80211_QOS_HAS_SEQ(wh)) {
if (length < sizeof(struct ieee80211_qosframe)) return;
}
4. Use Static Analysis in Your CI Pipeline
Tools that can catch this class of issue include:
- Orbis AppSec — detected this exact vulnerability automatically
- Coverity — tracks tainted length values through call chains
- CodeQL — can model buffer access patterns in C/C++
- Semgrep — custom rules for protocol parsing patterns
5. Fuzz the Wireless Receive Path
Fuzz testing is particularly effective for frame-parsing code. Tools like libFuzzer or AFL++ can be pointed at ieee80211_input() with a corpus of valid 802.11 frames and will quickly generate undersized and malformed inputs that expose missing bounds checks.
6. Reference Standards
- CWE-120: Buffer Copy without Checking Size of Input
- OWASP: Buffer Overflow
- CERT C Coding Standard: Rule ARR38-C — "Guarantee that library functions do not form invalid pointers"
Key Takeaways
- Never assume a received wireless frame meets a minimum size. The
ieee80211_input()function acceptedlengthdirectly from hardware without any prior validation — always check before you parse. sizeof(struct ieee80211_frame)is your minimum gate for data frames. Any code path that dereferences a frame pointer must be preceded by this comparison.- A single missing comparison can expose an entire device to remote attack. The vulnerable code path required zero authentication — just proximity to the device's wireless interface.
- The fix is one line, but the audit should be broader. The control and management frame branches in the same
switchstatement deserve the same scrutiny applied here to the data frame branch. - Firmware buffer overflows are especially severe. Unlike application-layer bugs, there is no OS sandbox, ASLR, or stack canary to rely on in many embedded environments — making the absence of input validation directly exploitable.
How Orbis AppSec Detected This
- Source: Raw 802.11 frame data received from the wireless hardware interface, passed as
uint8_t *framewith attacker-controlleduint32_t lengthintoieee80211_input(). - Sink:
ieee80211_input_data(frame, length, rssi)atsrc/firmware/src/net/ieee80211.c:1605, which dereferencesframeas astruct ieee80211_frame *without any prior length validation. - Missing control: No check that
length >= sizeof(struct ieee80211_frame)before forwarding toieee80211_input_data(). - CWE: CWE-120 — Buffer Copy without Checking Size of Input ("Classic Buffer Overflow").
- Fix: Inserted
if(length < sizeof(struct ieee80211_frame)) return;immediately before theieee80211_input_data()call, blocking all undersized frames from reaching the vulnerable parsing logic.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
The vulnerability in ieee80211_input() is a textbook example of how a single missing bounds check in a binary protocol parser can open a critical attack surface — in this case, one reachable by any attacker within wireless range, requiring no credentials and no prior interaction with the device. The fix is elegantly simple: one comparison, if(length < sizeof(struct ieee80211_frame)) return;, inserted at exactly the right point in the data-frame processing path.
For developers working on firmware, drivers, or any C code that processes externally-sourced binary data: make length validation the first thing you write, not an afterthought. Treat every uint8_t *buf, size_t len pair as hostile until proven otherwise, and always use sizeof() of your target structure as the minimum acceptable length before you touch a single field.