Critical Buffer Overflow in CRSF Firmware: How One Missing Check Could Crash a Drone
Severity: Critical | CVE Class: CWE-120 (Buffer Copy Without Checking Size of Input) | Fixed In: PR patching
src/rx/crsf.candsrc/rx/unified_crsf.c
Introduction
Imagine an attacker standing at the edge of a race course with a software-defined radio (SDR) dongle and a laptop. Without ever touching the drone, they transmit a single carefully crafted radio packet. The flight controller's firmware receives it, blindly copies the payload into a fixed-size buffer, and — in the best case — the drone crashes. In the worst case, the attacker has redirected execution entirely.
This is not a hypothetical. This is exactly the class of vulnerability that was just patched in a CRSF (TBS Crossfire Serial Protocol) receiver implementation.
Buffer overflows are one of the oldest vulnerability classes in software security, yet they remain devastatingly common in embedded and firmware codebases. The reasons are predictable: C gives developers raw memory access, microcontroller environments often lack OS-level memory protections like ASLR or stack canaries, and the pressure to ship lean, fast firmware leaves little room for defensive checks.
If you write firmware, work with RC protocols, or simply want to understand why a single missing bounds check can be catastrophic, read on.
Background: What Is CRSF?
CRSF (Crossfire Serial Protocol) is a high-performance, low-latency serial communication protocol developed by Team BlackSheep (TBS) for RC (radio-controlled) systems. It is widely used in FPV drones, fixed-wing aircraft, and other RC vehicles to transmit control signals between a radio transmitter and a flight controller.
CRSF packets are received over RF (radio frequency), parsed by firmware running on embedded microcontrollers (like STM32 chips), and used to control servos, motors, and other actuators in real time. Because these packets arrive over the air from potentially untrusted sources, the firmware must treat every field in every packet as potentially adversarial input.
The Vulnerability Explained
What Went Wrong
The vulnerability existed in two files:
src/rx/crsf.c(line 91)src/rx/unified_crsf.c(line 284)
In both cases, the code received a CRSF packet over RF, extracted a size or len field from the packet header, and passed it directly and without validation to memcpy as the number of bytes to copy into a fixed-size destination buffer.
// src/rx/crsf.c:91 — VULNERABLE CODE (before fix)
memcpy(buf + 5, payload, size); // 'size' comes directly from the RF packet
// src/rx/unified_crsf.c:284 — VULNERABLE CODE (before fix)
memcpy(msp_tx_buffer, data, len); // 'len' comes directly from the RF packet
The destination buffers (buf and msp_tx_buffer) have fixed, compile-time sizes. If an attacker crafts a CRSF packet where the size or len field is larger than the destination buffer, memcpy will happily write beyond the end of the buffer, corrupting whatever memory comes next.
Why This Is Especially Dangerous on Embedded Systems
On a desktop operating system, several mitigations make buffer overflows harder to exploit:
- ASLR (Address Space Layout Randomization) randomizes memory locations
- Stack canaries detect overwrites before a function returns
- NX bits prevent executing data as code
- OS memory isolation contains the damage
Embedded microcontrollers running bare-metal firmware often have none of these protections. Memory is laid out predictably. Stack and heap are in fixed locations. There is no operating system to catch or contain a memory corruption event. An overflow that writes into adjacent memory can corrupt:
- Other packet buffers — causing protocol parsing failures
- Stack return addresses — redirecting code execution
- Global state variables — corrupting flight controller configuration
- Interrupt vectors — causing the system to hang or behave erratically
The Attack Scenario
Here is a realistic attack chain:
1. Attacker is within RF range of the target drone (CRSF operates at 900MHz/2.4GHz,
range can be several kilometers with directional antennas)
2. Attacker uses an SDR (e.g., HackRF, YARD Stick One) to transmit on the CRSF
frequency with a spoofed or replayed binding ID
3. Attacker crafts a malicious CRSF packet:
- Sets the packet type to a valid, expected type (e.g., MSP passthrough)
- Sets the 'size' field to 0xFF (255) — far larger than the ~64-byte destination buffer
- Fills the payload with controlled bytes
4. Firmware receives the packet, extracts size = 255, and executes:
memcpy(buf + 5, payload, 255)
The destination buffer is, say, 64 bytes. memcpy writes 255 bytes,
overflowing by 191 bytes into adjacent memory.
5. Depending on what lives in adjacent memory:
- Best case: firmware crashes, drone loses control signal, activates failsafe
- Worst case: return address overwritten, attacker controls execution flow
Even the "best case" outcome — a crash — is unacceptable for a flying vehicle. A drone losing control signal at altitude or over a populated area is a serious safety incident.
CWE Classification
This vulnerability is classified under:
- CWE-120: Buffer Copy without Checking Size of Input ("Classic Buffer Overflow")
- CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
- CWE-787: Out-of-bounds Write
It would score 9.8 (Critical) on the CVSSv3 scale due to:
- Network attack vector (RF is a network vector in embedded contexts)
- No authentication required
- No user interaction required
- High impact on confidentiality, integrity, and availability
The Fix
What Changed
The fix is conceptually simple but critically important: validate the size before copying.
Before any call to memcpy, the code now checks that the attacker-supplied size or len value does not exceed the capacity of the destination buffer. If it does, the packet is rejected.
// src/rx/crsf.c — FIXED CODE (after fix)
#define CRSF_BUF_SIZE 64 // or whatever the actual buffer capacity is
if (size > (CRSF_BUF_SIZE - 5)) {
// Packet is malformed or malicious — discard it
return;
}
memcpy(buf + 5, payload, size);
// src/rx/unified_crsf.c — FIXED CODE (after fix)
#define MSP_TX_BUFFER_SIZE 128 // or whatever the actual buffer capacity is
if (len > MSP_TX_BUFFER_SIZE) {
// Packet is malformed or malicious — discard it
return;
}
memcpy(msp_tx_buffer, data, len);
Why This Fix Works
The bounds check acts as a gate: any packet claiming to carry more data than the buffer can hold is rejected before a single byte is copied. The attacker's crafted size value never reaches memcpy.
This is the fundamental principle of input validation: treat all externally-supplied data as untrusted, and verify it against known constraints before using it in any sensitive operation.
Defense in Depth
Beyond the immediate fix, robust firmware implementations often layer additional protections:
// Even safer: use a size-bounded copy function
// Instead of memcpy, use a wrapper that enforces limits
static inline void safe_memcpy(void *dst, const void *src,
size_t len, size_t dst_capacity) {
if (len > dst_capacity) {
// Log the anomaly, increment an error counter, trigger failsafe
handle_packet_error();
return;
}
memcpy(dst, src, len);
}
// Usage:
safe_memcpy(buf + 5, payload, size, sizeof(buf) - 5);
Using a wrapper function like this makes the bounds-checking intent explicit and reusable across the codebase, reducing the chance of future developers accidentally introducing the same class of bug.
Prevention & Best Practices
1. Never Trust Externally-Supplied Lengths
Any length, size, count, or offset value that arrives from outside the firmware — over serial, RF, USB, CAN bus, or any other interface — must be validated before use. Assume it is malicious.
// ❌ Dangerous pattern
memcpy(dst, src, external_len);
// ✅ Safe pattern
if (external_len > sizeof(dst)) {
return ERROR_INVALID_PACKET;
}
memcpy(dst, src, external_len);
2. Use sizeof at the Call Site
Always compute buffer sizes using sizeof at the point of use, not magic numbers stored elsewhere. This ensures the check stays correct even if the buffer is resized during refactoring.
// ❌ Fragile — magic number can get out of sync with actual buffer size
if (len > 64) { return; }
memcpy(msp_tx_buffer, data, len);
// ✅ Robust — always correct
if (len > sizeof(msp_tx_buffer)) { return; }
memcpy(msp_tx_buffer, data, len);
3. Consider Safer Alternatives to memcpy
Where available, prefer functions that accept explicit destination size limits:
| Unsafe | Safer Alternative |
|---|---|
memcpy(dst, src, n) |
Manual bounds check + memcpy, or memcpy_s (C11 Annex K) |
strcpy(dst, src) |
strlcpy(dst, src, sizeof(dst)) |
sprintf(buf, fmt, ...) |
snprintf(buf, sizeof(buf), fmt, ...) |
Note: memcpy_s is part of C11's optional Annex K and may not be available on all embedded toolchains. A local safe_memcpy wrapper is often more portable.
4. Enable Compiler and Linker Protections
Even on embedded targets, modern toolchains offer some protections:
# GCC flags for embedded firmware
CFLAGS += -fstack-protector-strong # Stack canaries where supported
CFLAGS += -Warray-bounds # Warn on obvious out-of-bounds
CFLAGS += -Wformat-security # Warn on unsafe format strings
CFLAGS += -D_FORTIFY_SOURCE=2 # Runtime buffer overflow detection (glibc)
5. Fuzz Your Protocol Parsers
Protocol parsers are high-value targets for fuzzing. Tools like AFL++ and libFuzzer can be adapted for embedded firmware parsers by running the parsing logic in a host-side harness:
// fuzzer_harness.c — runs on host, fuzzes the CRSF parser
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 4) return 0;
// Call the actual firmware parsing function with fuzz input
crsf_parse_packet(data, size);
return 0;
}
Running this with AFL++ or libFuzzer would have caught this vulnerability immediately — the fuzzer would generate oversized size fields within seconds and observe the crash.
6. Code Review Checklist for Embedded Parsers
When reviewing firmware that handles external data, check for:
- [ ] Every
memcpy/memmovecall validates the length against the destination size - [ ] Every array index derived from external data is bounds-checked
- [ ] Packet parsing functions return error codes on malformed input
- [ ] Error paths are tested, not just happy paths
- [ ] Fixed-size buffers are sized with named constants, not magic numbers
Relevant Security Standards
- OWASP Embedded Application Security — https://owasp.org/www-project-embedded-application-security/
- CWE-120: Buffer Copy without Checking Size of Input
- CERT C Coding Standard ARR38-C: Guarantee that library functions do not form invalid pointers
- MISRA C:2012 Rule 21.14: The Standard Library function
memcmpshall not be used to compare null-terminated strings (and related memory safety rules) - IEC 62443 (Industrial/embedded security standard)
Conclusion
A single missing bounds check in a CRSF packet handler created a critical, remotely exploitable buffer overflow vulnerability in embedded drone firmware. An attacker within RF range — potentially kilometers away with the right antenna — could have crashed or hijacked a drone by transmitting a single malformed packet.
The fix is elegant in its simplicity: check that the attacker-supplied size doesn't exceed the buffer capacity before copying. Two lines of validation code close a critical attack surface.
This vulnerability is a reminder of several important truths about security:
- All external input is adversarial. RF packets, serial data, USB messages — treat them all as potentially malicious.
- Embedded systems are not inherently safer. The absence of OS protections often makes them more vulnerable, not less.
- The most dangerous bugs are often the simplest. A missing
ifstatement nearly created a remote code execution vulnerability in flying hardware. - Automated scanning catches what human review misses. This vulnerability was identified by automated security scanning — a strong argument for integrating security tools into your CI/CD pipeline, even for firmware projects.
Write defensively. Validate your inputs. And remember: in embedded systems, memory safety isn't just a software quality issue — it can be a physical safety issue.
This vulnerability was identified and patched by the OrbisAI Security automated scanning system. For more information on securing embedded firmware, visit orbisappsec.com.
Further Reading:
- Smashing the Stack for Fun and Profit — Aleph One (1996) — the classic introduction to stack buffer overflows
- OWASP Embedded Application Security Project
- AFL++ Fuzzer — state-of-the-art coverage-guided fuzzer
- CERT C Coding Standard