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-overflow#buffer-overflow#c-security#ax25#memory-safety#embedded-security#cwe-122

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

Critical Buffer Overflow in zlib: When sprintf() Becomes a Security Nightmare

A critical buffer overflow vulnerability was discovered and patched in a bundled zlib123 library, where the use of unsafe sprintf() and vsprintf() functions allowed attackers to overwrite adjacent memory by supplying specially crafted compressed data. This type of vulnerability can lead to remote code execution, making it one of the most severe classes of security bugs in systems programming. The fix addresses the root cause by replacing or constraining the unsafe function calls that lacked buff

critical

Critical Stack Buffer Overflow Fixed in sgl_log.c: What You Need to Know

A critical stack buffer overflow vulnerability was discovered and patched in `source/core/sgl_log.c`, where unsafe use of `strcpy` and `memcpy` without bounds checking could allow attackers to overwrite stack memory, corrupt return addresses, and potentially execute arbitrary code. This fix eliminates a classic CWE-120 vulnerability that has plagued C codebases for decades and serves as a timely reminder of why bounds-checked string operations are non-negotiable in systems programming. Understan

critical

Buffer Overflow via strcpy(): How Unsafe String Copies Crash Programs and Compromise Security

A critical buffer overflow vulnerability was discovered and patched in `src/utils/utils.c`, where five unguarded calls to `strcpy()` allowed attacker-controlled strings from external configuration files to overwrite stack and heap memory. This class of vulnerability — one of the oldest and most dangerous in systems programming — can lead to arbitrary code execution, privilege escalation, or full application compromise. The fix replaces unsafe string operations with bounds-checked alternatives, c

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