Back to Blog
high SEVERITY9 min read

Heap Buffer Overflow in SSL/TLS: When Proto Length Goes Wrong

A critical heap buffer overflow vulnerability was discovered and patched in `src/ssl.c`, where improper bounds checking during ALPN/NPN protocol list construction could allow an attacker to corrupt heap memory and potentially execute arbitrary code. The fix addresses both the missing capacity validation and a dangerous integer overflow in size arithmetic that could lead to undersized allocations followed by out-of-bounds writes. Understanding this class of vulnerability is essential for any deve

O
By Orbis AppSec
Published May 11, 2026Reviewed June 3, 2026

Answer Summary

A heap buffer overflow (CWE-122) was discovered in C code handling SSL/TLS ALPN/NPN protocol negotiation in `src/ssl.c`. The vulnerability occurred when protocol list construction lacked proper bounds checking and contained an integer overflow in size calculations, allowing undersized heap allocations followed by out-of-bounds writes. The fix adds explicit capacity validation before writes and uses safe integer arithmetic to prevent overflow conditions during buffer size calculations.

Vulnerability at a Glance

cweCWE-122
fixAdded capacity validation and safe integer arithmetic for buffer allocation
riskRemote code execution via heap memory corruption during SSL/TLS handshake
languageC
root causeMissing bounds checking and integer overflow in ALPN protocol list size calculation
vulnerabilityHeap Buffer Overflow

Heap Buffer Overflow in SSL/TLS: When Proto Length Goes Wrong

Severity: Critical | CVE Class: Heap Buffer Overflow | Component: SSL/TLS Protocol Negotiation (src/ssl.c)


Introduction

Memory safety bugs in SSL/TLS libraries are among the most dangerous vulnerabilities in existence. They sit at the intersection of two uncomfortable truths: cryptographic code is notoriously difficult to get right, and memory corruption bugs in C can have catastrophic consequences. The infamous Heartbleed vulnerability (CVE-2014-0160) reminded the entire industry just how devastating a single bounds-check omission in OpenSSL could be — exposing private keys, session tokens, and sensitive data at scale.

The vulnerability we're examining today follows a similar pattern: a missing bounds check during protocol list construction in an SSL implementation's ALPN/NPN negotiation code. Combined with an integer overflow in size arithmetic, this creates a classic "allocate too small, write too much" scenario that attackers have exploited for decades.

If you write C code that handles network protocols, cryptographic libraries, or any user-controlled binary data, this post is required reading.


The Vulnerability Explained

Background: ALPN and NPN Protocol Negotiation

Application-Layer Protocol Negotiation (ALPN) and its predecessor Next Protocol Negotiation (NPN) are TLS extensions that allow a client and server to agree on which application protocol to use (e.g., HTTP/2, HTTP/1.1, gRPC) during the TLS handshake — without adding extra round trips.

When configuring an SSL context in code (for example, via a Lua binding or a configuration API), a developer can specify a list of supported protocols. Internally, these protocols are packed into a binary buffer using a length-prefixed format:

[length_byte][protocol_string][length_byte][protocol_string]...

For example, the protocol list for ["h2", "http/1.1"] would be encoded as:

\x02h2\x08http/1.1

This buffer is then passed to the SSL library. The vulnerability lives in the code that builds this buffer.


The Vulnerable Code Pattern

The vulnerability exists at src/ssl.c:1021 and involves two distinct but related flaws:

Flaw 1: Missing Bounds Check Before memcpy

// VULNERABLE CODE (illustrative reconstruction)
static int ssl_set_protocols(SSL_CTX *ctx, const char **protocols, int count) {
    unsigned char proto_list[MAX_PROTO_LIST_LEN];
    size_t proto_list_len = 0;

    for (int i = 0; i < count; i++) {
        size_t proto_len = strlen(protocols[i]);

        proto_list[proto_list_len] = (unsigned char)proto_len;
        proto_list_len++;

        // ❌ NO CHECK: Is there enough space remaining in proto_list?
        memcpy(proto_list + proto_list_len, protocols[i], proto_len);
        proto_list_len += proto_len;
    }

    return SSL_CTX_set_alpn_protos(ctx, proto_list, proto_list_len);
}

The critical omission: there is no check that proto_list_len + proto_len is within the bounds of proto_list before calling memcpy. If an attacker supplies a crafted protocol name that is long enough, or enough protocol names in aggregate, the memcpy will write past the end of the buffer into adjacent heap memory.

Flaw 2: Integer Overflow in Size Arithmetic

In a dynamically allocated variant of the same pattern, the allocation size is computed as:

// VULNERABLE CODE (illustrative reconstruction)
size_t total_size = proto_list_len + proto_len; // ❌ Can overflow!
unsigned char *proto_list = realloc(proto_list, total_size);

If proto_list_len is near SIZE_MAX (the maximum value of a size_t), adding proto_len causes the result to wrap around to a small number. The realloc call then allocates a tiny buffer, but the subsequent memcpy writes the full, large amount of data — a classic integer overflow → heap overflow chain.


How Could This Be Exploited?

Attack Surface

The vulnerability is reachable through the Lua ALPN/NPN SSL configuration API, meaning any user or application that can pass protocol names through this interface could trigger the overflow. Depending on the deployment:

  • A malicious configuration file specifying crafted protocol names
  • A Lua script with attacker-controlled input passed to the SSL API
  • A network-facing service that allows dynamic SSL configuration

Exploitation Scenario

Here's a realistic attack chain:

1. Attacker identifies a service using this SSL library with Lua bindings
2. Attacker crafts a protocol name that is 255+ bytes long, or supplies
   many short protocol names totaling > MAX_PROTO_LIST_LEN bytes
3. The memcpy overflows the heap buffer, overwriting adjacent heap metadata
   or other security-sensitive objects (function pointers, SSL session data)
4. Depending on heap layout:
   - Crash (Denial of Service)
   - Heap metadata corruption  arbitrary write primitive
   - Control flow hijack  Remote Code Execution

The Integer Overflow Path

proto_list_len = SIZE_MAX - 5  (e.g., 18446744073709551610 on 64-bit)
proto_len      = 10

total_size = (SIZE_MAX - 5) + 10
           = SIZE_MAX + 5
           = 4  (after 64-bit wraparound!)  ← allocates 4 bytes

memcpy(proto_list, data, 10)  ← writes 10 bytes into a 4-byte buffer

This is textbook CWE-190 (Integer Overflow) feeding directly into CWE-122 (Heap-Based Buffer Overflow).


Real-World Impact

Impact Category Description
Confidentiality Heap contents (keys, session data, credentials) may be leaked
Integrity Heap corruption can allow overwriting security-sensitive structures
Availability Malformed input reliably crashes the process
Code Execution On exploitable heap layouts, full RCE is possible

The CVSS score for a vulnerability of this class in a network-facing SSL component routinely reaches 9.0–10.0 (Critical).


The Fix

The patch addresses both root causes: the missing capacity check and the integer overflow in size arithmetic.

Fixed Code Pattern

// FIXED CODE (illustrative)
#define MAX_PROTO_LIST_LEN 512  // or appropriate constant

static int ssl_set_protocols(SSL_CTX *ctx, const char **protocols, int count) {
    unsigned char proto_list[MAX_PROTO_LIST_LEN];
    size_t proto_list_len = 0;

    for (int i = 0; i < count; i++) {
        size_t proto_len = strlen(protocols[i]);

        // ✅ FIX 1: Validate proto_len is a valid single-byte length prefix value
        if (proto_len == 0 || proto_len > 255) {
            return -1;  // Protocol names must be 1-255 bytes per TLS spec
        }

        // ✅ FIX 2: Check for integer overflow before arithmetic
        if (proto_len > MAX_PROTO_LIST_LEN - proto_list_len - 1) {
            return -1;  // Not enough space remaining
        }

        // ✅ FIX 3: Bounds are now verified — safe to write
        proto_list[proto_list_len] = (unsigned char)proto_len;
        proto_list_len++;

        memcpy(proto_list + proto_list_len, protocols[i], proto_len);
        proto_list_len += proto_len;
    }

    return SSL_CTX_set_alpn_protos(ctx, proto_list, proto_list_len);
}

For the dynamic allocation variant:

// FIXED dynamic allocation
// ✅ Check for integer overflow BEFORE computing allocation size
if (proto_len > SIZE_MAX - proto_list_len) {
    return -1;  // Would overflow
}

size_t new_size = proto_list_len + proto_len;
unsigned char *new_list = realloc(proto_list, new_size);
if (new_list == NULL) {
    return -1;  // Allocation failure
}
proto_list = new_list;

What the Fix Actually Does

Problem Fix Applied
No bounds check before memcpy Added explicit remaining-capacity check before each write
Integer overflow in proto_list_len + proto_len Added overflow-safe arithmetic check before size computation
No validation of proto_len value Added range check (1–255) per TLS protocol specification
Realloc result not checked Added NULL check after realloc

The fix follows the "validate inputs before use" principle — ensuring that all size values are within expected ranges before any memory operations take place.


Prevention & Best Practices

1. Always Validate Lengths Before Memory Operations

Never trust a length value — even one that came from your own code earlier in the call stack. Before any memcpy, memmove, or strcpy:

// Pattern: always check remaining capacity
if (src_len > dst_capacity - dst_used) {
    handle_error();
}
memcpy(dst + dst_used, src, src_len);

2. Use Overflow-Safe Arithmetic

In C, unsigned integer overflow is defined behavior (it wraps), but it's almost never what you want in security-sensitive code. Use explicit overflow checks:

// Safe addition check
if (b > SIZE_MAX - a) {
    // Would overflow
    return -1;
}
size_t result = a + b;

On modern compilers, you can also use built-in overflow-checking functions:

// GCC/Clang built-ins (preferred in new code)
size_t result;
if (__builtin_add_overflow(a, b, &result)) {
    return -1;
}

3. Prefer Safe Abstractions

When possible, avoid raw buffer management entirely:

  • Use dynamic strings (e.g., strbuf, custom growable buffers with built-in bounds checking)
  • Use memory-safe languages (Rust, Go) for new SSL/TLS tooling where feasible
  • Use existing well-tested libraries (OpenSSL, BoringSSL, mbedTLS) rather than rolling your own protocol negotiation code

4. Enable Compiler and Runtime Protections

These won't prevent all exploits, but they raise the bar significantly:

# Compiler flags for security hardening
CFLAGS += -fstack-protector-strong
CFLAGS += -D_FORTIFY_SOURCE=2
CFLAGS += -fsanitize=address,undefined  # During development/testing
LDFLAGS += -Wl,-z,relro,-z,now

AddressSanitizer (ASan) would have caught this vulnerability immediately in testing:

==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
WRITE of size 200 at 0x... thread T0
    #0 memcpy
    #1 ssl_set_protocols  src/ssl.c:1021

5. Fuzz Your SSL Code

Protocol parsing and negotiation code is an ideal target for fuzzing:

# Using AFL++ or libFuzzer on the protocol list construction
# Provide a corpus of valid protocol name lists
# The fuzzer will quickly find length boundary conditions
afl-fuzz -i corpus/ -o findings/ -- ./ssl_proto_fuzz_target @@

6. Code Review Checklist for C Memory Operations

When reviewing C code involving buffers, ask:

  • [ ] Is every memcpy/memmove preceded by a bounds check?
  • [ ] Is every size arithmetic operation checked for integer overflow?
  • [ ] Are all malloc/realloc return values checked for NULL?
  • [ ] Are length values validated against protocol/spec constraints?
  • [ ] Is the buffer size clearly documented and enforced?

Relevant Security Standards and References

Standard Reference
CWE-122 Heap-Based Buffer Overflow
CWE-190 Integer Overflow or Wraparound
CWE-20 Improper Input Validation
OWASP Buffer Overflow
CERT C MEM35-C: Allocate sufficient memory for an object
CERT C INT30-C: Ensure unsigned integer operations do not wrap
CVE Reference CVE-2014-0160 (Heartbleed) — similar class of SSL bounds-check failure

Conclusion

This vulnerability is a textbook example of how two individually subtle bugs — a missing bounds check and an integer overflow — can combine to create a critical, potentially exploitable memory corruption issue in security-critical code. The fact that it lives in SSL/TLS protocol negotiation code makes it especially dangerous, since this component handles the very infrastructure we rely on to protect sensitive communications.

Key Takeaways

  1. Length values are attacker-controlled data — treat them with the same skepticism as any other user input
  2. Integer overflow + heap overflow is a classic chain — always check arithmetic before allocation
  3. AddressSanitizer and fuzzing would catch this — integrate them into your CI pipeline
  4. Validate against the spec — TLS protocol names are 1–255 bytes; enforce that constraint at the boundary
  5. Defense in depth matters — compiler hardening flags won't prevent the bug, but they make exploitation significantly harder

Memory safety in C is hard, but it's not magic. The patterns for safe buffer handling are well-established and well-documented. The challenge is discipline: applying these patterns consistently, especially in the complex, performance-sensitive code paths where SSL implementations live.

The next time you write a memcpy, ask yourself: have I actually proven that there's enough space in the destination? That one question, asked consistently, prevents an entire class of critical vulnerabilities.


This vulnerability was identified and patched by the OrbisAI Security automated scanning system. Automated security scanning, combined with human review, is a powerful combination for catching memory safety issues before they reach production.


Further Reading
- Heartbleed: The Full Story
- CERT C Coding Standard: Memory Management
- Google Project Zero: Exploiting the Linux kernel via packet sockets
- Chromium's Rule of 2: Memory Safety

Frequently Asked Questions

What is a heap buffer overflow?

A heap buffer overflow occurs when a program writes data beyond the allocated boundaries of a heap buffer, corrupting adjacent memory and potentially allowing attackers to execute arbitrary code or crash the application.

How do you prevent heap buffer overflow in C?

Prevent heap buffer overflows by always validating buffer capacity before writes, using safe integer arithmetic to avoid overflow in size calculations, employing bounds-checked functions, and validating all input lengths before processing.

What CWE is heap buffer overflow?

Heap buffer overflow is classified as CWE-122 (Heap-based Buffer Overflow), a subset of CWE-787 (Out-of-bounds Write) and related to CWE-190 (Integer Overflow or Wraparound).

Is using malloc() with calculated sizes enough to prevent heap buffer overflow?

No, malloc() with calculated sizes is not sufficient. You must also validate that size calculations don't overflow, check that the allocated buffer has sufficient capacity before each write, and ensure input lengths are validated against maximum limits.

Can static analysis detect heap buffer overflow?

Yes, static analysis tools can detect many heap buffer overflow patterns, especially those involving missing bounds checks or obvious integer overflow conditions. However, complex data flows may require dynamic analysis or manual code review.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #407

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.