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, 2026•Reviewed 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.


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 targets — info->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.


Prevention and further reading

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++ and how to fix it

A critical buffer overflow in `create_hex_string()` within `hmlangw.cpp` let an unconditional 16-iteration loop write past the bounds of a 100-byte `hex` buffer using unchecked `sprintf` calls. The fix replaces `sprintf` with `snprintf` and caps the loop iterations based on the actual destination buffer size, closing off a memory corruption path reachable from serial or network input.

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation

medium

How Integer Overflow happens in C++ image processing and how to fix it

A signed integer overflow in OpenCV's `bilateralFilter.cpp` allowed the buffer size calculation `cal_width * cal_height * cn` to wrap around to a small or negative value, causing `padding.resize()` to allocate far less memory than needed. Subsequent `memcpy` operations would then write beyond the allocated buffer, creating a heap corruption primitive. The fix is a single targeted cast to `size_t` that promotes the multiplication to unsigned 64-bit arithmetic before any overflow can occur.

critical

How Stack Buffer Overflows Happen in C with sprintf() and How to Fix Them

A critical stack buffer overflow was discovered in `libuv/Learn-libuv/docs/code/tty-gravity/main.c` where `sprintf()` wrote ANSI escape sequences and user-controlled variables into a fixed 500-byte buffer without any bounds checking. An attacker controlling the `pos`, `width`, or `message` variables could overflow the stack, overwrite return addresses, and potentially achieve arbitrary code execution. The fix replaces `sprintf()` with `snprintf()` and adds explicit length validation to ensure wr

high

How insecure string copy functions happen in C and how to fix them

A high-severity buffer overflow risk was discovered in `login/main.c` where `strcpy()` was used to copy the `HOME` environment variable into a fixed-size 512-byte buffer without any bounds checking. An attacker controlling the `HOME` environment variable could overflow `pwd_file_name`, potentially corrupting memory or hijacking execution. The fix replaces the two-step `strcpy`/`strcat` pattern with a single, bounds-safe `snprintf` call.