Back to Blog
critical SEVERITY9 min read

How buffer overflow in handle_interlink_event() happens in C terminal event handling and how to fix it

A critical buffer overflow vulnerability was discovered in `src/terminal/event.c` at line 250, where `memcpy()` blindly copied `info->cwd` into a fixed-size `term->cwd` buffer without verifying the source string's actual length. An attacker who could supply a crafted working directory path longer than `MAX_CWD_LEN` could corrupt adjacent heap memory, potentially leading to code execution. The fix replaces the unsafe `memcpy()` call with `safe_strncpy()`, which enforces the destination buffer bou

O
By Orbis AppSec
Published June 29, 2026Reviewed June 29, 2026

Answer Summary

This is a heap buffer overflow vulnerability (CWE-122) in C, found in `handle_interlink_event()` inside `src/terminal/event.c`. The root cause is a `memcpy()` call that copies `info->cwd` into the fixed-size `term->cwd` buffer using `MAX_CWD_LEN` as the copy length — but never checks whether `info->cwd` is actually shorter than that limit, allowing oversized input to corrupt adjacent memory. The fix replaces `memcpy(term->cwd, info->cwd, MAX_CWD_LEN)` with `safe_strncpy(term->cwd, info->cwd, MAX_CWD_LEN)`, which respects the destination buffer size and null-terminates the result safely.

Vulnerability at a Glance

cweCWE-122
fixReplace memcpy() with safe_strncpy() to enforce destination buffer boundary
riskHeap memory corruption leading to potential code execution or crash
languageC
root causememcpy() copies up to MAX_CWD_LEN bytes from info->cwd without verifying the source fits within the destination buffer
vulnerabilityHeap Buffer Overflow via unchecked memcpy()

How Buffer Overflow in handle_interlink_event() Happens in C Terminal Event Handling and How to Fix It

Summary

A critical buffer overflow vulnerability was found in src/terminal/event.c at line 250, inside the handle_interlink_event() function. A memcpy() call copied the current working directory string info->cwd into the fixed-size term->cwd buffer using MAX_CWD_LEN as the byte count — without ever checking whether the source string was actually that long or shorter. An attacker who could feed a crafted terminal event with an oversized cwd field could corrupt adjacent heap memory. The fix is a one-line change: replace memcpy() with safe_strncpy().


Introduction

The src/terminal/event.c file is responsible for processing interlink events that carry terminal state — things like the terminal name, environment variables, and the current working directory. Inside handle_interlink_event(), the code takes an incoming struct interlink_event, extracts its info payload, and copies fields into the live struct terminal object.

One of those field copies looked like this at line 250:

memcpy(term->cwd, info->cwd, MAX_CWD_LEN);
term->cwd[MAX_CWD_LEN - 1] = 0;

At first glance this looks defensive — it copies exactly MAX_CWD_LEN bytes and then null-terminates. But there is a subtle and dangerous assumption buried here: that info->cwd actually contains MAX_CWD_LEN or fewer bytes of meaningful data. If an attacker supplies a cwd string that is longer than MAX_CWD_LEN, memcpy() will happily read past the intended boundary of the source and write past the end of the destination buffer, corrupting whatever lives next to term->cwd on the heap.


The Vulnerability Explained

What memcpy() Does (and Doesn't Do)

memcpy(dest, src, n) copies exactly n bytes from src to dest. It does not care about null terminators. It does not check whether src is actually n bytes long. It does not check whether dest has room for n bytes beyond what was allocated.

The code allocates term->cwd with a buffer of size MAX_CWD_LEN. The intent is clearly to limit the copy to that size. But the third argument to memcpy() controls how many bytes are read from the source — not how many bytes are available in the destination. If info->cwd points to a string of 512 bytes and MAX_CWD_LEN is 256, memcpy() will write 256 bytes into term->cwd — filling it exactly — which sounds fine. But it will also have read 256 bytes from a source that may only have a 256-byte allocation itself, and any attacker-controlled data in adjacent source memory gets pulled in.

More critically: if info->cwd is an attacker-controlled buffer that is larger than MAX_CWD_LEN and the destination term->cwd was allocated at exactly MAX_CWD_LEN, then writing MAX_CWD_LEN bytes into it is safe for the destination — but the null-termination line term->cwd[MAX_CWD_LEN - 1] = 0 only protects the last byte of the destination. It does nothing to prevent the copy from reading attacker-controlled garbage from the source side.

The real danger is the inverse scenario: info->cwd is shorter than MAX_CWD_LEN but memcpy() copies MAX_CWD_LEN bytes anyway, reading garbage or attacker-crafted bytes from memory beyond the actual string. This can leak sensitive data or, if the attacker controls that adjacent memory, inject unexpected content into term->cwd.

The Vulnerable Code

// src/terminal/event.c, line 250 (before fix)
memcpy(term->cwd, info->cwd, MAX_CWD_LEN);
term->cwd[MAX_CWD_LEN - 1] = 0;

The problem: memcpy() copies a fixed number of bytes (MAX_CWD_LEN) regardless of the actual length of info->cwd. No validation of strlen(info->cwd) occurs before this call.

Exploitation Scenario

This is a local CLI tool, meaning exploitation requires the attacker to control command-line arguments or input files that ultimately populate terminal event data. In that threat model, a concrete attack looks like this:

  1. The attacker crafts an input file or command-line argument that causes info->cwd to point to a string significantly longer than MAX_CWD_LEN.
  2. handle_interlink_event() is called with this crafted event.
  3. memcpy(term->cwd, info->cwd, MAX_CWD_LEN) copies MAX_CWD_LEN bytes from the oversized source into term->cwd.
  4. Depending on heap layout, the bytes immediately following term->cwd on the heap — which could be another struct field, a function pointer, or heap metadata — are overwritten with attacker-controlled data.
  5. This heap corruption can lead to a crash (denial of service) or, with heap grooming, arbitrary code execution.

The PR also flags src/terminal/event.c:531 as using the same pattern, meaning the attack surface exists in at least two places.


The Fix

What Changed

The fix is a single-line substitution in handle_interlink_event():

// BEFORE (vulnerable)
memcpy(term->cwd, info->cwd, MAX_CWD_LEN);
term->cwd[MAX_CWD_LEN - 1] = 0;

// AFTER (fixed)
safe_strncpy(term->cwd, info->cwd, MAX_CWD_LEN);

Two lines became one. The explicit null-termination line is gone because safe_strncpy() handles that internally.

Why This Fix Works

safe_strncpy() is a bounds-aware string copy function that:

  1. Copies at most MAX_CWD_LEN - 1 characters from info->cwd into term->cwd.
  2. Always null-terminates term->cwd[MAX_CWD_LEN - 1], regardless of source length.
  3. Stops copying when it encounters a null terminator in the source — unlike memcpy(), which is oblivious to string semantics.

This means that even if info->cwd is 10,000 characters long, safe_strncpy() will copy at most MAX_CWD_LEN - 1 characters and then write a null byte. The destination buffer is never overrun, and the result is always a properly terminated C string.

The Regression Test

A new test file test/test_invariant_event.c was added alongside the fix. It exercises three cases:

  • A payload longer than MAX_CWD_LEN (the exploit case)
  • A payload exactly MAX_CWD_LEN - 1 characters (the boundary case)
  • A normal short path like /home/user (the valid input case)

For each case, the test fills the memory after term->cwd with a sentinel value (0xBB) and verifies it is unchanged after the copy. This is a canonical heap overflow detection technique and will catch any future regression that reintroduces an unbounded copy.


Prevention & Best Practices

Never Use memcpy() for String Copies Without Length Validation

memcpy() is for copying binary blobs of known, validated size. When copying C strings from user-influenced sources, always use a function that respects null terminators:

Function Safe? Notes
strcpy() No bounds checking at all
strncpy() ⚠️ Does not guarantee null termination
strlcpy() BSD; always null-terminates, returns required length
safe_strncpy() Project-specific wrapper; always null-terminates
snprintf(dest, n, "%s", src) Portable; always null-terminates

Validate Input Length Before Any Copy

If you must use memcpy() for a string-like operation, validate first:

size_t src_len = strnlen(info->cwd, MAX_CWD_LEN);
memcpy(term->cwd, info->cwd, src_len);
term->cwd[src_len] = '\0';

This ensures you only copy as many bytes as actually exist in the source string.

Review All memcpy() Call Sites in Event Handlers

The PR explicitly notes that src/terminal/event.c:531 uses the same pattern. Any code that processes externally-influenced event data and uses memcpy() for string fields should be audited. A targeted Semgrep rule can find these patterns automatically:

rules:
  - id: memcpy-string-no-length-check
    pattern: memcpy($DEST, $SRC, $LEN);
    message: Verify $SRC length does not exceed $LEN before memcpy
    languages: [c]
    severity: WARNING

Relevant Standards

  • CWE-122: Heap-based Buffer Overflow
  • CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
  • OWASP: Input Validation — always validate the length and format of externally-supplied data before using it in memory operations
  • SEI CERT C Coding Standard: STR31-C — Guarantee that storage for strings has sufficient space for character data and the null terminator

Key Takeaways

  • memcpy() with a fixed length is not a bounds check — it tells memcpy() how many bytes to copy, not how many bytes the source contains. In handle_interlink_event(), passing MAX_CWD_LEN as the size was intended as a limit but acted as an unconditional copy length.
  • String fields in event structs are high-risk copy targetsinfo->cwd comes from external event data in a terminal interlink protocol, making it an attacker-reachable source. Any memcpy() of such fields without a preceding strlen() check is a potential overflow.
  • safe_strncpy() eliminates the dual-write anti-pattern — the original code used memcpy() followed by a manual null-termination write. This two-step pattern is fragile; safe_strncpy() handles both atomically and correctly.
  • The same pattern exists at line 531 — fixing one instance is not enough. A full audit of memcpy() calls on string fields throughout event.c is warranted.
  • Sentinel-based regression tests are the right tool here — filling memory after the destination buffer with a known value and asserting it is unchanged after the copy is a reliable, low-overhead way to catch buffer overflow regressions in C code.

How Orbis AppSec Detected This

  • Source: The info->cwd field within the struct interlink_event payload, populated from externally-supplied terminal event data
  • Sink: memcpy(term->cwd, info->cwd, MAX_CWD_LEN) at src/terminal/event.c:250 inside handle_interlink_event()
  • Missing control: No validation of strlen(info->cwd) or strnlen(info->cwd, MAX_CWD_LEN) before the copy; the fixed copy length MAX_CWD_LEN was mistakenly treated as a safety bound rather than a copy count
  • CWE: CWE-122 — Heap-based Buffer Overflow
  • Fix: Replaced memcpy(term->cwd, info->cwd, MAX_CWD_LEN) and the manual null-termination with a single call to safe_strncpy(term->cwd, info->cwd, MAX_CWD_LEN), which enforces the destination buffer limit and guarantees null termination

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 buffer overflow in handle_interlink_event() is a textbook example of how a well-intentioned but subtly incorrect use of memcpy() creates a critical security vulnerability in C. The developer clearly intended to limit the copy to MAX_CWD_LEN bytes — the constant is right there in the call. But memcpy() does not interpret its third argument as a safety limit on the destination; it interprets it as an exact byte count to copy from the source. When the source is attacker-influenced event data, that distinction is the difference between safe code and a heap corruption primitive.

The fix is minimal and correct: one function call replaces two lines, and safe_strncpy() provides the string-aware, null-termination-guaranteed copy semantics that the original code was trying to achieve. Combined with the new regression test in test/test_invariant_event.c, this fix is both immediately effective and durably protected against future regressions.

If you work with C code that processes event-driven or protocol-driven data, audit every memcpy() call that touches string fields. The pattern of copying a "known maximum" number of bytes from an externally-influenced source is one of the most common and consequential mistakes in systems programming.


References

Frequently Asked Questions

What is a buffer overflow in C?

A buffer overflow occurs when a program writes more data into a fixed-size memory buffer than it can hold, overwriting adjacent memory. In C, functions like memcpy() and strcpy() do not automatically check bounds, making them common sources of this vulnerability.

How do you prevent buffer overflows in C with memcpy?

Always validate that the source data length does not exceed the destination buffer size before calling memcpy(). Better yet, use safer alternatives like safe_strncpy() or strlcpy() that enforce the destination limit and guarantee null termination.

What CWE is buffer overflow?

Heap-based buffer overflows are classified as CWE-122. Stack-based buffer overflows are CWE-121. The general category of buffer copy without checking input size is CWE-120.

Is using MAX_CWD_LEN as the memcpy size enough to prevent buffer overflow?

No. Passing MAX_CWD_LEN as the copy length to memcpy() tells it how many bytes to copy from the source — it does not check whether the source is shorter than that limit. If the source buffer contains attacker-controlled data longer than MAX_CWD_LEN, the copy will read beyond the source's intended boundary and write past the destination buffer.

Can static analysis detect buffer overflows like this one?

Yes. Static analysis tools including Semgrep, Coverity, CodeQL, and the Orbis AppSec multi-agent AI scanner can detect patterns where memcpy() is called without a preceding length validation, flagging them as potential buffer overflow sinks.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #433

Related Articles

critical

How buffer overflow happens in C dlldbg.c sprintf() and how to fix it

A classic buffer overflow vulnerability was discovered in `bld/pbide/dlldbg/dlldbg.c` at line 80, where an unbounded `sprintf()` call wrote a user-influenced `dllName` string into a fixed-size `fmtBuffer` without any length checking. An attacker supplying a maliciously crafted DLL name could overflow the buffer, overwrite adjacent memory, and potentially achieve arbitrary code execution. The fix replaces `sprintf()` with `snprintf()`, passing `sizeof(fmtBuffer)` as an explicit bound to ensure th

critical

How buffer overflow in strcpy() happens in C bin2coff tool and how to fix it

A critical buffer overflow vulnerability was discovered in `tools/bin2coff.c` where multiple `strcpy()` operations copied user-controlled label strings into fixed-size buffers without bounds checking. An attacker could provide maliciously long labels to overflow destination buffers and corrupt adjacent memory structures, potentially leading to arbitrary code execution. The fix replaced unsafe string operations with bounded alternatives like `strlcpy()` and `snprintf()`.

high

How buffer overflow happens in C string operations with strcpy/strncpy and how to fix it

A critical buffer overflow vulnerability in `src/pomoc.c` was discovered where `strncpy()` was used unsafely to copy a socket path into a fixed-size buffer. The fix replaces the dangerous string copy with `snprintf()`, which provides automatic bounds checking and null-termination. This prevents attackers from exploiting the CLI tool through oversized input arguments.

high

How integer overflow in malloc happens in C libregexp and how to fix it

A high-severity integer overflow vulnerability was discovered in QuickJS's libregexp.c where multiplication to compute allocation size could wrap around, causing a heap overflow. The fix replaces the unsafe `malloc(sizeof(capture[0]) * lre_get_alloc_count(bc))` pattern with `calloc(lre_get_alloc_count(bc), sizeof(capture[0]))`, which safely handles the multiplication internally and prevents exploitation.

critical

How buffer overflow via sprintf() happens in C++ settings parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `app/src/main/cpp/samp/settings.cpp` where `sprintf()` writes to a fixed 127-byte buffer (`char buff[0x7F]`) without bounds checking. If the `g_pszStorage` global variable contains a string longer than ~107 bytes, the formatted output exceeds the buffer, enabling stack corruption. The fix replaces `sprintf()` with `snprintf()` using `sizeof(buff)` to guarantee writes never exceed the declared buffer length.

critical

How integer overflow in js_realloc_array() happens in C QuickJS and how to fix it

A confirmed integer overflow vulnerability in QuickJS's `js_realloc_array()` function could allow attackers to trigger heap under-allocation by supplying crafted JavaScript input. The fix adds a pre-multiplication bounds check that prevents `new_size * elem_size` from wrapping around `SIZE_MAX`. This closes a critical code execution path that existed in the production JavaScript engine.