Back to Blog
critical SEVERITY10 min read

Heap Buffer Overflow in AX.25 Packet Parsing: How a Missing Bounds Check Could Let Attackers Hijack Your System

A critical heap buffer overflow vulnerability was discovered and patched in `src/ax25.c`, where a `memcpy` operation blindly trusted an attacker-controlled packet length field without validating it against the destination buffer's allocated size. This class of vulnerability is particularly dangerous because it allows remote attackers — anyone who can transmit an AX.25 packet over RF or a network feed — to corrupt heap memory, potentially leading to arbitrary code execution. The fix introduces pr

O
By orbisai0security
May 16, 2026

Heap Buffer Overflow in AX.25 Packet Parsing: How a Missing Bounds Check Could Let Attackers Hijack Your System

Severity: 🔴 Critical | CVE Class: Heap-Based Buffer Overflow (CWE-122) | Attack Vector: Remote (RF or Network)


Introduction

In the world of amateur radio and packet networking, the AX.25 protocol is a cornerstone — a link-layer protocol used by ham radio operators, satellite ground stations, and embedded communication systems worldwide. It's the kind of infrastructure that quietly hums in the background, often assumed to be safe because of its niche audience.

That assumption just got a reality check.

A critical heap buffer overflow vulnerability was identified and patched in src/ax25.c — the kind of bug that security researchers describe as a "write primitive," a foundational building block for full remote code execution. The root cause? A single memcpy call that trusted a number it should never have trusted: a length value supplied directly by the remote sender of an AX.25 packet.

If you write C or C++ code that parses network packets, reads binary data from external sources, or works with embedded protocol stacks, this post is required reading. The pattern is devastatingly common, and the consequences are severe.


The Vulnerability Explained

What Is AX.25?

AX.25 is a data link layer protocol derived from the X.25 standard, designed for use over amateur radio. An AX.25 packet contains several fields, including a variable-length information field — the payload of the packet. When software receives an AX.25 packet, it typically:

  1. Allocates a buffer to hold the information field.
  2. Reads the info_len field from the packet header to know how many bytes to copy.
  3. Uses memcpy (or equivalent) to copy the payload into the allocated buffer.

Step 3 is where things went wrong.

The Vulnerable Code Pattern

The vulnerability exists at src/ax25.c:208. Here's the conceptual pattern of what the vulnerable code looked like:

// Vulnerable pattern — DO NOT USE
int parse_ax25_packet(const uint8_t *raw_buf, size_t raw_len, ax25_frame_t *out) {

    // info_len is read DIRECTLY from the incoming packet
    out->info_len = read_uint16(raw_buf, OFFSET_INFO_LEN);

    // A buffer is allocated — but based on what size?
    // If the allocation uses a fixed size or a different calculation,
    // this is already dangerous.
    out->information = malloc(MAX_INFO_SIZE);
    if (!out->information) return -1;

    // ⚠️ CRITICAL: info_len comes from the attacker.
    // There is NO check that info_len <= MAX_INFO_SIZE.
    memcpy(out->information, raw_buf + OFFSET_INFO_DATA, out->info_len);
    //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //     If info_len > MAX_INFO_SIZE, this writes PAST the buffer!

    return 0;
}

The problem is elegant in its simplicity: the code allocates a buffer of a certain size, but copies a number of bytes controlled by the attacker. If the attacker sets info_len to a value larger than the allocated buffer, memcpy will happily write bytes beyond the end of the heap allocation.

Why Is This So Dangerous?

On the heap, memory allocations sit next to each other. When you overflow one allocation, you start overwriting the contents of adjacent allocations. In a modern heap layout, those adjacent regions might contain:

  • Heap metadata (chunk headers used by malloc/free) — corrupting these can cause free() to execute attacker-controlled code
  • Function pointers — overwriting a stored callback means the next time it's called, the attacker's code runs instead
  • Other data structures — credentials, session tokens, or configuration data
  • vtable pointers (in C++ code) — leading directly to virtual dispatch hijacking

This transforms a "simple" memory corruption into a potential remote code execution (RCE) primitive.

The Attack Scenario

Let's walk through a realistic attack:

[Attacker's Radio/Network Node]
        |
        |  Crafted AX.25 packet:
        |    info_len = 0xFFFF  (65535 bytes)
        |    actual payload = 512 bytes of shellcode + padding
        |
        v
[Vulnerable AX.25 Receiver]
        |
        |  malloc(512)  → allocates 512-byte heap buffer
        |  memcpy(..., 0xFFFF)  → writes 65535 bytes!
        |                          ^^^^^^^^^^^^^^^^^^^^^^
        |                          Overwrites adjacent heap chunks
        v
[Heap Corruption → Crash or Code Execution]

What makes this especially alarming:

  1. No authentication required. AX.25 operates at the link layer. Anyone within RF range — or connected to a network feed — can send packets. There's no login, no handshake, no credential check before the vulnerable code runs.

  2. The attack is remote. An attacker doesn't need physical access to the machine running this software. A malicious packet transmitted over radio or injected into a network APRS feed is sufficient.

  3. The attack is reliable. Heap overflows, especially with full control over the overflow size and content, are among the most exploitable memory corruption vulnerabilities. Modern exploit techniques like heap grooming can make these highly reliable even with ASLR enabled.


The Fix

What Changed

The fix introduces bounds validation before the memcpy call. The principle is straightforward: before copying N bytes into a buffer of size M, verify that N ≤ M. If the check fails, the packet is rejected as malformed.

Here's the corrected pattern:

// Fixed version — proper bounds checking
int parse_ax25_packet(const uint8_t *raw_buf, size_t raw_len, ax25_frame_t *out) {

    // Read info_len from the packet (still attacker-controlled)
    out->info_len = read_uint16(raw_buf, OFFSET_INFO_LEN);

    // ✅ FIX #1: Validate info_len against the maximum allowed size
    if (out->info_len > MAX_INFO_SIZE) {
        log_error("AX.25: info_len %u exceeds maximum %u, dropping packet",
                  out->info_len, MAX_INFO_SIZE);
        return -EINVAL;  // Reject the malformed packet
    }

    // ✅ FIX #2: Also validate against the actual raw buffer length
    // to prevent reading past the input buffer (a separate vulnerability)
    if (OFFSET_INFO_DATA + out->info_len > raw_len) {
        log_error("AX.25: packet truncated, dropping");
        return -EINVAL;
    }

    // Allocate exactly what we need (or use a pre-allocated buffer)
    out->information = malloc(out->info_len);
    if (!out->information) return -ENOMEM;

    // ✅ Now safe: we've verified info_len <= MAX_INFO_SIZE
    //              and the source data is within raw_buf bounds
    memcpy(out->information, raw_buf + OFFSET_INFO_DATA, out->info_len);

    return 0;
}

Key Security Improvements

Before After
info_len used directly without validation info_len checked against MAX_INFO_SIZE
No check against raw buffer bounds Validates info_len fits within raw_len
Attacker controls copy length Copy length bounded by constants
Heap overflow possible Malformed packets rejected before copy

The Two-Check Pattern

Notice that the fix includes two separate bounds checks, not one. This is intentional and important:

  1. Check against the destination buffer size — prevents heap overflow on write
  2. Check against the source buffer size — prevents heap over-read on read (a separate vulnerability that could leak memory contents)

Both checks are necessary. A fix that only adds one of them is incomplete.


Prevention & Best Practices

This vulnerability belongs to one of the oldest and most well-documented vulnerability classes in existence. Here's how to prevent it systematically.

1. Never Trust Length Fields From External Sources

Any length, size, count, or offset value that arrives from a network packet, file, or other external input is attacker-controlled. Treat it as hostile until proven otherwise.

// ❌ Dangerous pattern
size_t len = get_length_from_packet(pkt);
memcpy(dst, src, len);  // len could be anything

// ✅ Safe pattern
size_t len = get_length_from_packet(pkt);
if (len > sizeof(dst_buffer)) {
    return ERROR_INVALID_INPUT;
}
memcpy(dst, src, len);

2. Use Safer Copy Functions

Where possible, prefer length-bounded alternatives:

// Prefer these over memcpy/strcpy when dealing with external data:
memcpy_s()     // C11 Annex K — bounds-checked memcpy
strlcpy()      // bounds-checked string copy (BSD/POSIX)
snprintf()     // for string formatting with length limit

// In C++, prefer:
std::copy_n()  // with explicit bounds
std::span<>    // C++20 bounds-aware view

3. Consider Memory-Safe Languages for Protocol Parsers

The Rust programming language, in particular, makes this entire class of vulnerability structurally impossible. Rust's slice types carry their length, and indexing operations are bounds-checked by default:

// In Rust, this is a compile-time and runtime-safe operation
fn parse_info_field(raw: &[u8], info_len: usize) -> Result<Vec<u8>, ParseError> {
    if info_len > MAX_INFO_SIZE {
        return Err(ParseError::InvalidLength);
    }
    // Rust's slice indexing will panic (not silently corrupt memory)
    // if info_len > raw.len()
    Ok(raw[..info_len].to_vec())
}

For new protocol parser implementations, strongly consider Rust, Go, or other memory-safe languages.

4. Use Static Analysis Tools

Several tools can detect this pattern automatically:

Tool Type Detects
Coverity Static Analysis Buffer overflows, tainted data flows
CodeQL Semantic Analysis Taint tracking from network input to memcpy
AddressSanitizer (ASan) Runtime Heap overflows at runtime during testing
Valgrind Runtime Memory errors including overflows
Clang Static Analyzer Static Analysis Memory safety issues in C/C++
Semgrep Pattern Matching Custom rules for dangerous patterns

Add these to your CI/CD pipeline. ASan in particular is trivial to enable and catches heap overflows immediately during testing:

# Compile with AddressSanitizer
gcc -fsanitize=address -g -o myprogram myprogram.c

# Or with CMake
cmake -DCMAKE_C_FLAGS="-fsanitize=address" ..

5. Adopt a "Parse, Don't Validate" Architecture

Structure your packet parsers so that all external data is validated in one place before it's used anywhere else. The parser either returns a fully validated, safe data structure, or it returns an error — there's no middle ground.

// Good architecture: parser validates everything upfront
typedef struct {
    uint8_t  src_addr[7];
    uint8_t  dst_addr[7];
    uint8_t  information[MAX_INFO_SIZE];  // Fixed-size, always safe
    uint16_t info_len;                    // Guaranteed <= MAX_INFO_SIZE
} ax25_frame_validated_t;

// Returns NULL on any validation failure
ax25_frame_validated_t *ax25_parse(const uint8_t *buf, size_t len);

6. Security Standards & References

This vulnerability maps to several well-known security standards:

  • CWE-122: Heap-based Buffer Overflow
  • CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
  • CWE-20: Improper Input Validation
  • OWASP: Buffer Overflow
  • CERT C Coding Standard: Rule ARR38-C (Guarantee that library functions do not form invalid pointers)
  • MISRA C:2012: Rule 1.3 (Undefined behavior), Rule 21.17 (Standard library string/memory functions)

Conclusion

A single missing bounds check in a memcpy call. That's all it takes to turn a packet parser into a remote code execution vulnerability. This isn't a theoretical concern — heap buffer overflows are regularly weaponized in real-world attacks, and AX.25 implementations are particularly exposed because they accept input from anyone within radio range, with no authentication barrier.

The fix is simple: validate before you copy. Check that the attacker-supplied length fits within your buffer. Check that it fits within the source data. Reject anything that doesn't comply. This three-line fix closes an attack surface that could have allowed a malicious actor to execute arbitrary code on any system running this software.

Key Takeaways

  • Never trust length fields from external input — always validate against known bounds
  • Apply two-sided bounds checks — validate against both source and destination sizes
  • Add ASan to your test suite — it catches these bugs immediately during development
  • Use static analysis in CI/CD — tools like CodeQL can find taint flows from network input to memcpy
  • Consider memory-safe languages for new protocol parser implementations
  • Treat all network-facing code as adversarial — assume the worst about every field in every packet

Security is not about perfection — it's about making exploitation as difficult as possible. Proper bounds checking is one of the most fundamental and effective defenses in the C programmer's toolkit. Use it, every time, without exception.


This vulnerability was identified and patched as part of an automated security review. If you maintain software that processes AX.25 packets or other binary protocols, audit your memcpy call sites today.

Fixed by OrbisAI Security — automated security analysis for modern codebases.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #209

Related Articles

critical

Heap Corruption in Dynamic App Loaders: How Unvalidated Binary Size Fields Open the Door to Memory Attacks

A critical heap corruption vulnerability was discovered in a dynamic application loader where size values read directly from untrusted binary files were used to drive memory operations without any bounds validation. An attacker supplying a crafted app binary could overflow heap buffers, corrupt memory, and potentially achieve arbitrary code execution. The fix introduces strict bounds checks before memory operations and replaces unsafe allocation patterns with overflow-safe alternatives.

critical

Integer Overflow to Heap Corruption: Fixing a Critical Buffer Overflow in ENet

A critical integer overflow vulnerability was discovered in `include/enet.h` where size calculations derived from attacker-controlled network values could overflow before being passed to `enet_malloc`, resulting in undersized heap allocations and subsequent heap corruption. The fix adds proper bounds checking to sector I/O code, preventing attackers from triggering heap overflows by sending crafted network packets. This class of vulnerability is particularly dangerous in networked applications b

critical

Critical Buffer Overflow in ELF Parser: How a Missing Bounds Check Almost Became a Heap Exploit

A critical out-of-bounds memory vulnerability was discovered and patched in `utils/symbol-rawelf.c`, where two separate `memcpy` calls lacked proper bounds validation when processing ELF binary files. Without these checks, a maliciously crafted ELF file could trigger an out-of-bounds read or heap overflow, potentially leading to remote code execution or memory corruption. This post breaks down how the vulnerability works, how it was fixed, and what every C developer should know about safe memory

critical

Critical DNS Integer Overflow: How a +1 Nearly Enabled Remote Code Execution

A critical integer overflow vulnerability in DNS record processing code could have allowed a malicious DNS server to trigger a heap buffer overflow, potentially enabling remote code execution. The fix ensures safe bounds checking before performing size calculations, closing a subtle but devastating attack vector that lurks in network-facing C code.

critical

Critical BLE Buffer Overflow Fixed: How Heap Overflows Put IoT Devices at Risk

A critical heap buffer overflow vulnerability was discovered and patched in a BLE (Bluetooth Low Energy) characteristic write handler, where missing bounds checks allowed any nearby Bluetooth device to send oversized payloads and potentially execute arbitrary code. This fix adds essential buffer-length validation before memory copy operations, closing a remote attack vector that required zero authentication to exploit. Understanding this class of vulnerability is essential for any developer work

critical

Heap Buffer Overflow in BLE Stack: How a Missing Bounds Check Could Let Attackers Crash or Hijack Devices

A critical heap buffer overflow vulnerability was discovered and patched in `ble_spam.c`, where two consecutive `memcpy` calls copied attacker-controlled data into fixed-size heap buffers without validating the copy length first. An attacker within Bluetooth range could exploit this flaw to crash the target device, corrupt memory, or potentially execute arbitrary code — all without any authentication. The fix adds a proper bounds check before the copy operations, ensuring the length derived from