Back to Blog
medium SEVERITY8 min read

Buffer Overflow in Freestanding Runtime: How Unsafe strcpy() Puts Bare-Metal Systems at Risk

A critical buffer overflow vulnerability was discovered in the freestanding runtime's custom string library, where `strcpy()` and `memcpy()` implementations lacked any bounds checking whatsoever. In a bare-metal or kernel-like environment with no OS-level memory protection, this flaw could allow an attacker to overwrite adjacent memory regions — including function pointers and security-critical state — with arbitrary data. The fix introduces a safe `strlcpy()` implementation that enforces destin

O
By orbisai0security
May 28, 2026

Buffer Overflow in Freestanding Runtime: How Unsafe strcpy() Puts Bare-Metal Systems at Risk

Introduction

When most developers think about buffer overflows, they picture web servers or desktop applications crashing under a fuzzer's relentless probing. But some of the most dangerous buffer overflows live in a quieter, more treacherous place: freestanding (bare-metal) runtimes — the low-level C environments that power embedded systems, hypervisors, and custom kernels.

In these environments, there's no operating system to catch a rogue memory write. There's no ASLR to make exploitation unpredictable. There's no stack canary inserted by a distro's hardened toolchain. When a buffer overflows here, it overwrites whatever is next in memory — and that might be a function pointer, a security policy flag, or a cryptographic key.

This post breaks down a critical (CWE-120) buffer overflow vulnerability found in a freestanding runtime's custom string library, explains how it could be exploited, and walks through the strlcpy()-based fix that closes the door on unbounded string copies.


The Vulnerability Explained

What Is a Freestanding Runtime?

A "freestanding" C environment is one that doesn't rely on a hosted standard library (like glibc or MSVC's CRT). Instead, the project provides its own implementations of fundamental functions like memcpy, strlen, and strcpy. This is common in:

  • Embedded firmware (microcontrollers, IoT devices)
  • Bootloaders and UEFI modules
  • Custom kernels and hypervisors
  • WebAssembly runtimes compiled to bare metal

Because these environments have no OS underneath them, memory protection is entirely the programmer's responsibility.

The Vulnerable Code

The freestanding runtime in question implemented strcpy() like this:

// VULNERABLE - avs/runtime/freestanding/src/string/str.c
char *strcpy(char *dest, const char *src) {
    char *d = dest;
    while ((*d++ = *src++) != '\0');
    return dest;
}

And memcpy() similarly lacked any length validation against the destination buffer's actual capacity.

The problem is fundamental, not incidental. The strcpy() signature is:

char *strcpy(char *dest, const char *src);

Notice what's missing? There's no size parameter. The function has absolutely no way to know how large dest is. It will copy bytes from src until it hits a null terminator — no matter how many bytes that takes, and no matter how small dest is.

CWE-120: Buffer Copy Without Checking Size of Input

This is a textbook instance of CWE-120: "Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')". The CVSS score for this finding was rated Critical, and rightfully so.

How Could It Be Exploited?

Consider this scenario in a freestanding kernel context:

// Somewhere in the kernel's command processing path
char kernel_cmd_buffer[64];         // Fixed-size stack buffer
security_policy_t *active_policy;   // Lives right after it in memory

// Attacker controls 'user_input' — e.g., from a serial console or network packet
strcpy(kernel_cmd_buffer, user_input);  // 💥 No bounds check

If user_input is longer than 63 bytes (leaving room for the null terminator), strcpy() happily keeps writing past the end of kernel_cmd_buffer. In a freestanding environment with no stack guard pages, it overwrites active_policy — or a return address, or a function pointer table.

Concrete attack scenarios include:

  1. Control-flow hijacking: Overwriting a function pointer stored adjacent to the destination buffer, redirecting execution to attacker-controlled shellcode.
  2. Security policy bypass: Overwriting a flag or pointer that controls access checks, privilege levels, or cryptographic key selection.
  3. Persistent corruption: In a kernel context, corrupting heap metadata or page table entries, leading to privilege escalation.
  4. Denial of service: Even without a clean exploit, corrupting memory causes unpredictable crashes — particularly dangerous in safety-critical embedded systems.

The absence of OS-level protections (ASLR, NX, stack canaries inserted by the OS loader) makes exploitation significantly more reliable than in a typical hosted environment.


The Fix

What Changed

The fix introduces strlcpy() — a safer alternative to strcpy() that was originally developed by OpenBSD — and adds it to both the header and implementation files.

Header change (avs/runtime/freestanding/include/string.h):

// BEFORE
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);

// AFTER
char *strcpy(char *dest, const char *src);
size_t strlcpy(char *dest, const char *src, size_t size);  // ← NEW
char *strncpy(char *dest, const char *src, size_t n);

Implementation (avs/runtime/freestanding/src/string/str.c):

// SAFE REPLACEMENT
size_t strlcpy(char *dest, const char *src, size_t size) {
    size_t src_len = strlen(src);
    if (size > 0) {
        size_t copy_len = (src_len >= size) ? size - 1 : src_len;
        char *d = dest;
        const char *s = src;
        size_t i = 0;
        while (i < copy_len) {
            d[i] = s[i];
            i++;
        }
        d[copy_len] = '\0';  // Always null-terminate
    }
    return src_len;  // Returns full source length for truncation detection
}

Why strlcpy() Is the Right Tool

strlcpy() addresses strcpy()'s fundamental design flaw by requiring the caller to pass the destination buffer size:

// Old, dangerous way
strcpy(kernel_cmd_buffer, user_input);

// New, safe way
strlcpy(kernel_cmd_buffer, user_input, sizeof(kernel_cmd_buffer));

Let's unpack the three security properties this implementation provides:

1. Hard Truncation at size - 1 Bytes

size_t copy_len = (src_len >= size) ? size - 1 : src_len;

If src is longer than the destination buffer, the copy is truncated to size - 1 bytes. The buffer cannot be overflowed, regardless of input length.

2. Guaranteed Null-Termination

d[copy_len] = '\0';

Unlike strncpy() (which famously does not null-terminate when truncation occurs), strlcpy() always writes a null terminator as long as size > 0. This prevents a whole class of read-overrun bugs that follow unterminated string operations.

3. Truncation Detection via Return Value

return src_len;  // Full length of src, NOT the number of bytes copied

The function returns the total length of the source string, not the number of bytes written. This allows callers to detect truncation:

size_t written = strlcpy(dest, src, sizeof(dest));
if (written >= sizeof(dest)) {
    // Truncation occurred — handle the error!
    log_error("Input truncated: source was %zu bytes, buffer is %zu", 
              written, sizeof(dest));
    return ERR_INPUT_TOO_LONG;
}

This is a significant improvement over strncpy(), which gives callers no indication that truncation occurred.

Comparison: strcpy vs strncpy vs strlcpy

Property strcpy strncpy strlcpy
Bounds checking ❌ None ✅ Truncates ✅ Truncates
Always null-terminates ✅ (if no overflow) ❌ Not on truncation ✅ Always
Detects truncation ✅ Via return value
Safe for untrusted input ⚠️ Partial ✅ Yes

Prevention & Best Practices

1. Ban strcpy() in Security-Sensitive Code

In any codebase that handles untrusted input — especially freestanding runtimes, parsers, and network stacks — strcpy() should be treated as a forbidden function. Add a compiler warning or linter rule:

# GCC/Clang: warn on dangerous string functions
CFLAGS += -Wdeprecated-declarations

Or use a static analysis tool (see below) to flag its use automatically.

2. Prefer strlcpy() Over strncpy()

Many developers reach for strncpy() as the "safe" alternative to strcpy(). It's not. strncpy() does not null-terminate the destination when truncation occurs, which leads to a different class of bugs. Use strlcpy() instead, and always check the return value for truncation.

3. Use Static Analysis Tools

Several tools can catch unbounded string operations automatically:

  • Clang Static Analyzer — Detects buffer overflows and dangerous function calls
  • Coverity — Enterprise-grade, excellent at CWE-120 detection
  • Flawfinder — Lightweight, specifically targets dangerous C/C++ patterns
  • CodeQL — GitHub-native, query-based analysis with buffer overflow rules
  • AddressSanitizer (ASan) — Runtime detection; invaluable during testing

4. Adopt a Secure String Library

For freestanding environments, consider adopting or auditing a well-reviewed secure string library:

  • safeclib — Implements the C11 Annex K "bounds-checking interfaces" (strcpy_s, memcpy_s, etc.)
  • OpenBSD's strlcpy/strlcat — Battle-tested, widely ported
  • C11 Annex K (strcpy_s) — If your toolchain supports it

5. Fuzz Your String-Handling Code

Buffer overflows in string functions are exactly what fuzzers are designed to find. Tools like AFL++ and libFuzzer can be pointed at any function that accepts string input and will quickly surface overflows that manual review misses.

6. Security Standards & References


Conclusion

Buffer overflows in custom string libraries are a reminder that security doesn't come for free when you roll your own primitives. In hosted environments, the OS and standard library provide a safety net of hardened implementations, ASLR, and stack canaries. In freestanding runtimes, that net doesn't exist — every unsafe function call is a direct line to memory corruption.

The key takeaways from this vulnerability and its fix:

  • strcpy() is inherently unsafe for untrusted input because it has no mechanism to know the destination buffer's size. Treat it as a forbidden function.
  • strncpy() is not a safe replacement — it doesn't null-terminate on truncation, which trades one bug class for another.
  • strlcpy() is the right tool: it truncates safely, always null-terminates, and returns the source length so callers can detect and handle truncation.
  • Freestanding environments demand extra diligence — the absence of OS-level memory protections makes buffer overflows far easier to exploit reliably.
  • Automated scanning works — this vulnerability was caught by an automated multi-agent AI scanner before it reached production. Invest in static analysis as part of your CI/CD pipeline.

Secure coding in C is hard, but it's not mysterious. The rules are well-understood, the tools to enforce them exist, and the fixes — like the strlcpy() implementation shown here — are straightforward. The only question is whether you apply them before or after an attacker does.


This vulnerability was automatically detected and fixed by OrbisAI Security. Automated security scanning can catch issues like this before they reach production.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #106

Related Articles

critical

Heap Buffer Overflow in Audio Ring Buffer: How a Missing Bounds Check Could Crash Your App

A critical heap buffer overflow vulnerability was discovered in `audio_backend.c`, where the audio ring buffer's `memcpy` operations lacked bounds validation before writing PCM data. Without checking that incoming data sizes fell within the allocated buffer's capacity, a maliciously crafted audio file could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix adds a concise pre-flight validation guard that rejects out-of-range write requests before any memory oper

critical

Critical Heap Buffer Overflow in SSDP Control Point: How Unbounded String Operations Put Networks at Risk

A critical heap buffer overflow vulnerability was discovered and patched in the SSDP control point implementation (`ssdp_ctrlpt.c`), where multiple unbounded `strcpy` and `strcat` operations constructed HTTP request buffers without any length validation. Network-received SSDP response fields — including service type strings and location URLs — could be crafted by an attacker to exceed buffer boundaries, potentially enabling arbitrary code execution or denial of service. The fix replaces the unsa

critical

Heap Buffer Overflow in OPDS Parser: How a Misplaced Variable Nearly Opened the Door to Remote Code Execution

A critical heap buffer overflow vulnerability was discovered in `lib/OpdsParser/OpdsParser.cpp`, where the buffer allocation size was calculated *after* a fixed chunk size was used to allocate memory, meaning the actual bytes read could exceed the allocated buffer. On embedded devices parsing untrusted OPDS catalog data from the network, this flaw could allow a remote attacker to corrupt heap memory and potentially achieve arbitrary code execution. The fix was elegantly simple: move the `toRead`

critical

Heap Buffer Overflow in BLE MIDI: How a Missing Bounds Check Opens the Door to Remote Exploitation

A critical heap buffer overflow vulnerability was discovered in the BLE MIDI packet assembly code of `blemidi.c`, where attacker-controlled packet length values could trigger writes beyond allocated heap memory. The fix adds an integer overflow guard before the `malloc` call, ensuring that maliciously crafted BLE MIDI packets can no longer corrupt heap memory. This vulnerability is particularly dangerous because it is remotely exploitable by any nearby Bluetooth device — no physical access requi

critical

Heap Overflow in TOML Parser: How Integer Overflow Leads to Memory Corruption

A critical heap buffer overflow vulnerability was discovered and patched in the centitoml TOML parser, where missing integer overflow validation on a `MALLOC(len+1)` call could allow an attacker to trigger memory corruption via a crafted TOML configuration file. The vulnerability (CWE-190) is reachable through community-distributed mod or map files that the game loads from its `config/` directory, making it a realistic attack vector for remote code execution. A targeted one-line guard now preven

critical

Heap Corruption via Unchecked memcpy: How Integer Overflow Bugs Corrupt Memory in Windows File Operations

A critical buffer overflow vulnerability was discovered in `phlib/nativefile.c`, where multiple `memcpy` calls copied filename and extended-attribute data into fixed-size structures without verifying that source lengths didn't exceed destination buffer boundaries. An attacker supplying an oversized filename or EA name could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix replaces unchecked arithmetic with Windows' safe integer helpers (`RtlULongAdd`, `RtlULon