Back to Blog
medium SEVERITY8 min read

How buffer overflow happens in C kernel PTY subsystem (tty_ptmx.c) and how to fix it

A stack buffer overflow vulnerability was discovered in `tty_ptmx.c`, the kernel-level pseudo-terminal multiplexer component, where an unchecked `sprintf()` call at line 293 could overflow the `device_name` buffer by combining `root_path` and `dev_rel_path` without bounds validation. Because this code executes in kernel context during PTY device creation, successful exploitation could lead to kernel memory corruption, privilege escalation, or system crashes. The fix replaces the unbounded `sprin

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

Answer Summary

This is a stack buffer overflow vulnerability (CWE-120) in the C kernel PTY subsystem (`components/lwp/terminal/tty_ptmx.c`), where `sprintf(device_name, "%s%s", root_path, dev_rel_path)` writes a formatted string into a fixed-size buffer without any length check. If the combined length of `root_path` and `dev_rel_path` exceeds the buffer, adjacent stack memory is overwritten. The fix replaces `sprintf()` with `snprintf(device_name, root_len + sizeof("/ptmx"), "%s%s", root_path, dev_rel_path)`, explicitly capping the write to the known buffer size and preventing overflow.

Vulnerability at a Glance

cweCWE-120
fixReplaced sprintf() with snprintf() using a precisely calculated size limit (root_len + sizeof("/ptmx"))
riskKernel memory corruption, privilege escalation, or system crash
languageC (kernel/embedded)
root causesprintf() used without length limit when concatenating two path strings into a fixed-size buffer
vulnerabilityStack Buffer Overflow via unbounded sprintf()

How buffer overflow happens in C kernel PTY subsystem (tty_ptmx.c) and how to fix it

Summary

A stack buffer overflow vulnerability was discovered in tty_ptmx.c, the kernel-level pseudo-terminal multiplexer component, where an unchecked sprintf() call at line 293 could overflow the device_name buffer by combining root_path and dev_rel_path without bounds validation. Because this code executes in kernel context during PTY device creation, successful exploitation could lead to kernel memory corruption, privilege escalation, or system crashes. The fix replaces the unbounded sprintf() with a properly bounded snprintf() call that explicitly limits output to the allocated buffer size.


Introduction

The components/lwp/terminal/tty_ptmx.c file is responsible for initializing pseudo-terminal multiplexer (PTY) devices in the RT-Thread Smart kernel. It handles the creation and registration of /dev/ptmx-style devices — the entry point for every terminal session spawned by the system. A flaw in the lwp_ptmx_init() function, specifically the sprintf() call at line 293, created a classic but dangerous stack buffer overflow condition.

The vulnerable line looks innocent at first glance:

sprintf(device_name, "%s%s", root_path, dev_rel_path);

But device_name is a fixed-size stack buffer, and neither root_path nor dev_rel_path are validated for length before this call. If an attacker or a misconfigured caller supplies path components whose combined length exceeds the buffer, sprintf() will happily write past the end of device_name and into adjacent kernel stack memory.

This matters enormously because lwp_ptmx_init() runs in kernel context. There is no userspace sandbox to contain the damage.


The Vulnerability Explained

What's happening at line 293

Inside lwp_ptmx_init(), the code allocates a buffer for the device name and then formats it using sprintf():

// VULNERABLE CODE (before fix) — tty_ptmx.c line 293
if (device_name)
{
    /* Register device */
    sprintf(device_name, "%s%s", root_path, dev_rel_path);
    rt_device_register(ptmx_device, device_name, 0);
    ...
}

The device_name buffer has a known, bounded size — it's allocated based on root_len + sizeof("/ptmx"). However, sprintf() is completely unaware of that size. It writes characters until the format string is exhausted, regardless of how much space remains. If root_path or dev_rel_path are longer than expected (due to a bug, misconfiguration, or deliberate manipulation), the write overflows the buffer.

Why this is especially dangerous in kernel context

In userspace, a stack buffer overflow typically corrupts the local stack frame and might be mitigated by ASLR, stack canaries, or NX bits. In kernel context, the stakes are higher:

  • Overwriting the kernel stack can corrupt return addresses, redirecting execution to attacker-controlled code.
  • It can corrupt adjacent kernel data structures, leading to privilege escalation.
  • Even without code execution, it can cause a kernel panic, taking down the entire system.

The companion issue at line 321

The PR also flagged a second unsafe string operation at line 321:

// VULNERABLE CODE (before fix) — tty_ptmx.c line 321
strncpy(buf, "pts/ptmx", len);

While strncpy() does accept a length argument, it has a subtle hazard: it does not guarantee null-termination if the source string exactly fills the destination buffer. Using snprintf() here is cleaner and unambiguously safe.

Attack scenario

Consider a scenario where root_path is supplied through a configuration interface or a mount namespace operation that allows longer-than-expected path strings. An attacker with the ability to influence root_path — for example, through a crafted filesystem mount point — could supply a string like:

/dev/pts/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...

Combined with dev_rel_path, the total length exceeds the device_name buffer. The sprintf() call writes beyond the buffer, overwriting the kernel stack. Depending on what lies adjacent in memory, this could overwrite a saved return address, enabling kernel-level code execution.


The Fix

Change 1: Replace sprintf() with snprintf() at line 293

The fix is precise and surgical:

// BEFORE (vulnerable)
sprintf(device_name, "%s%s", root_path, dev_rel_path);

// AFTER (fixed)
snprintf(device_name, root_len + sizeof("/ptmx"), "%s%s", root_path, dev_rel_path);

The second argument to snprintf()root_len + sizeof("/ptmx") — is exactly the size of the allocated device_name buffer. This means:

  • snprintf() will write at most root_len + sizeof("/ptmx") - 1 characters.
  • The output is always null-terminated.
  • Any excess input is silently truncated rather than overflowing the buffer.

This is the ideal fix because the size argument directly mirrors the allocation size, leaving no gap between what was allocated and what is written.

Change 2: Replace strncpy() with snprintf() at line 321

// BEFORE
strncpy(buf, "pts/ptmx", len);

// AFTER
snprintf(buf, len, "pts/ptmx");

snprintf() is strictly safer here: it always null-terminates, and the intent of the code (write a bounded string into a caller-supplied buffer) is more clearly expressed. The strncpy() function's non-termination behavior when len == strlen("pts/ptmx") is a known footgun that snprintf() eliminates entirely.

Change 3: Regression test infrastructure

The PR also adds a dedicated Kconfig option (RT_UTEST_LWP_TTY_PTMX) and a test case file (tty_ptmx_tc.c) to the utest framework:

config RT_UTEST_LWP_TTY_PTMX
    bool "Enable Utest for tty_ptmx buffer overflow regression (V-004)"
    depends on RT_USING_SMART
    default n

This ensures that future changes to tty_ptmx.c can be validated against a regression suite that explicitly tests buffer boundary conditions — a critical addition for kernel-level code where subtle regressions can be catastrophic.


Prevention & Best Practices

1. Treat sprintf() as deprecated in C kernel code

Every use of sprintf() in kernel or systems code should be considered a code smell. Modern C development guidelines — including the Linux kernel's own coding standards — strongly prefer snprintf() for all string formatting operations. Many static analysis tools can be configured to flag sprintf() as an error.

2. Pair allocation size with write size

When you allocate a buffer with a calculated size (e.g., rt_malloc(root_len + sizeof("/ptmx"))), the corresponding write operation should use that exact same size expression as the length limit. This is what the fix does:

// Allocation and write use the same size expression — easy to audit
char *device_name = rt_malloc(root_len + sizeof("/ptmx"));
snprintf(device_name, root_len + sizeof("/ptmx"), "%s%s", root_path, dev_rel_path);

3. Validate path lengths at the entry point

While the snprintf() fix prevents the overflow, it silently truncates oversized input. For correctness, consider adding an explicit length check before the format call:

if (strlen(root_path) + strlen(dev_rel_path) >= root_len + sizeof("/ptmx")) {
    // Log an error and return early
    return -RT_EINVAL;
}

This makes the failure mode explicit and auditable rather than silent.

4. Use compiler and linker hardening

Enable stack canaries (-fstack-protector-strong), FORTIFY_SOURCE (-D_FORTIFY_SOURCE=2), and address sanitization (-fsanitize=address) during development and testing. These mechanisms can catch overflow conditions that slip past code review.

5. Apply consistent safe-string wrappers

Consider introducing a project-wide safe_snprintf() wrapper that asserts on truncation during debug builds:

static inline int safe_snprintf(char *buf, size_t size, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    int n = vsnprintf(buf, size, fmt, args);
    va_end(args);
    RT_ASSERT(n >= 0 && (size_t)n < size);  // Catch truncation in debug builds
    return n;
}

Relevant standards

  • CWE-120: Buffer Copy without Checking Size of Input ("Classic Buffer Overflow")
  • CWE-121: Stack-based Buffer Overflow
  • OWASP: Buffer Overflow Prevention Cheat Sheet
  • SEI CERT C: STR07-C — Use the bounds-checking interfaces for string manipulation

Key Takeaways

  • sprintf() in lwp_ptmx_init() had no idea how large device_name was — the fix ties the write limit directly to the allocation size using root_len + sizeof("/ptmx").
  • Kernel-context overflows are categorically more dangerous than userspace ones — there is no process isolation or OS-level containment when the kernel stack is corrupted.
  • strncpy() is not a safe replacement for sprintf() — it doesn't guarantee null-termination; snprintf() does.
  • Regression tests belong in the build system — the new RT_UTEST_LWP_TTY_PTMX Kconfig entry ensures this specific overflow scenario is permanently guarded against future regressions.
  • The size expression used in rt_malloc() should be reused verbatim in the corresponding snprintf() call — this makes allocation/write pairs easy to audit and keeps the invariant obvious.

How Orbis AppSec Detected This

  • Source: The root_path parameter passed into lwp_ptmx_init(), which can be influenced by mount namespace configuration or device initialization paths.
  • Sink: sprintf(device_name, "%s%s", root_path, dev_rel_path) at components/lwp/terminal/tty_ptmx.c:293 — an unbounded write into a stack-allocated kernel buffer.
  • Missing control: No length check or bounded write function was used; sprintf() was called with two variable-length string arguments and a fixed-size destination with no size argument.
  • CWE: CWE-120 — Buffer Copy without Checking Size of Input ("Classic Buffer Overflow").
  • Fix: Replaced sprintf() with snprintf(device_name, root_len + sizeof("/ptmx"), "%s%s", root_path, dev_rel_path), capping the write to the exact allocation size.

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 sprintf()snprintf() change in tty_ptmx.c is a small diff with significant security consequences. In kernel-level C code, the difference between a bounded and unbounded string write is the difference between a safe device registration and a potential kernel compromise. This vulnerability is a reminder that even mature, well-reviewed systems code can harbor classic C pitfalls — and that automated static analysis is essential for catching them at scale.

The key principle to take away: every string write in C must be paired with an explicit size limit, and that limit must match the allocation. When working in kernel context, there is no safety net below you.


References

Frequently Asked Questions

What is a stack buffer overflow?

A stack buffer overflow occurs when a program writes more data into a fixed-size stack-allocated buffer than it can hold, overwriting adjacent memory — including return addresses or function pointers — which can lead to crashes or code execution.

How do you prevent buffer overflows in C?

Always use length-limited string functions like snprintf(), strlcpy(), or strlcat() instead of sprintf(), strcpy(), or strcat(). Always pass the exact buffer size as the length argument and validate input lengths before copying.

What CWE is buffer overflow?

Classic buffer overflows are classified under CWE-120 (Buffer Copy without Checking Size of Input), with related entries CWE-121 (Stack-based Buffer Overflow) and CWE-122 (Heap-based Buffer Overflow).

Is input validation enough to prevent buffer overflows in C?

Input validation helps but is not sufficient alone. You must also use bounded string functions (snprintf, strlcpy) and enforce buffer size limits at every copy site, because input validation can be bypassed or may not account for all code paths.

Can static analysis detect buffer overflows like this one?

Yes. Static analysis tools like Semgrep, Coverity, CodeChecker, and Clang's static analyzer can flag unsafe sprintf() and strcpy() patterns. Orbis AppSec's multi-agent AI scanner detected this exact pattern in tty_ptmx.c at line 293.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #11447

Related Articles

medium

How buffer overflow happens in C ImageMagick drawing-wand and how to fix it

ImageMagick's drawing-wand component contained a critical buffer overflow vulnerability in the MVGPrintf() function where vsprintf() was used without bounds checking. By switching to snprintf() with proper size constraints, the fix prevents attackers from overflowing the MVG buffer through crafted SVG files and achieving arbitrary code execution.

critical

How buffer overflow in URL parsing happens in C++ HTTP client and how to fix it

A critical buffer overflow vulnerability in the HTTP client's URL parsing function allowed attackers to overflow a stack-allocated host buffer through specially crafted URLs with excessively long hostnames. The vulnerability enabled arbitrary code execution by overwriting the return address. The fix adds proper bounds validation before the memcpy() operation to ensure the hostname length never exceeds the destination buffer size.

critical

How heap buffer overflow happens in C WiFi frame capture and how to fix it

A critical buffer overflow vulnerability in the ESP32 WiFi frame capture feature (feat_capture_hs.c) allowed attackers within WiFi range to craft oversized 802.11 frames that would overflow heap buffers and achieve remote code execution. The fix adds explicit length validation before memcpy operations and rejects oversized frames rather than silently truncating them.

critical

How integer overflow in _wopendir() happens in C Windows dirent and how to fix it

A critical integer overflow vulnerability in `include/compat/dirent_msvc.h` allowed an attacker-controlled directory path length to wrap the `sizeof(wchar_t) * n + 16` allocation calculation, resulting in a dangerously undersized heap buffer. Subsequent writes to that buffer caused a heap overflow, enabling potential memory corruption or code execution on Windows systems. The fix adds a pre-allocation bounds check and proper errno signaling to safely reject overflow-inducing inputs.

critical

How buffer overflow in SCSI command handling happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in libretro-common's CDROM handling code where the `cdrom_send_command_win32()` function copied an arbitrary number of bytes into a fixed 16-byte SCSI Command Descriptor Block (CDB) buffer without validation. This vulnerability could allow an attacker using a malicious CDROM image or USB device to corrupt memory and potentially execute arbitrary code. The fix adds a simple bounds check before the memcpy operation to ensure cmd_len never exc

medium

How path traversal happens in C file extraction and how to fix it

A path traversal vulnerability in the borpak archive extraction tool allowed attackers to write files to arbitrary locations on the filesystem by crafting malicious .pak archives with `../` sequences in filenames. This medium-severity issue in `tools/borpak/source/borpak.c` could enable system compromise through overwriting critical files like `.bashrc` or cron jobs. The fix implements path validation to ensure extracted files never escape the intended extraction directory.