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 Orbis AppSec
Published May 28, 2026Reviewed June 3, 2026

Answer Summary

This is a CWE-120 (Buffer Copy Without Checking Size of Input) buffer overflow vulnerability in a freestanding C runtime's custom string library. The `strcpy()` implementation wrote data into destination buffers without any bounds checking, enabling out-of-bounds memory writes in environments with no OS-level protection. The fix introduces a `strlcpy()` implementation that accepts an explicit `size` parameter and truncates copies to `size - 1` bytes, always null-terminating the result. This prevents adjacent memory — including function pointers and security state — from being overwritten by oversized input.

Vulnerability at a Glance

cweCWE-120
fixReplaced strcpy() with a strlcpy() implementation that takes an explicit destination size and truncates input to prevent out-of-bounds writes
riskAttacker-controlled data overwrites adjacent memory, including function pointers and security-critical state, with no OS protection to stop it
languageC (freestanding / bare-metal)
root causeCustom strcpy() and memcpy() implementations wrote to destination buffers without checking or enforcing any size limit
vulnerabilityBuffer Overflow (unbounded strcpy in freestanding runtime)

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.

Frequently Asked Questions

What is a buffer overflow in a freestanding C runtime?

It occurs when a string or memory copy function writes more bytes than the destination buffer can hold. In a freestanding (bare-metal) runtime there is no OS memory protection, so the overflow silently corrupts whatever data sits in adjacent memory — potentially function pointers, return addresses, or security flags.

How do you prevent buffer overflow in C bare-metal string libraries?

Replace unbounded functions like strcpy() with size-limited alternatives such as strlcpy() or strncpy(). Always pass the exact byte capacity of the destination buffer, and validate that the source length does not exceed it before copying.

What CWE is buffer overflow from strcpy()?

CWE-120 — "Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')". Related identifiers include CWE-122 (Heap-Based Buffer Overflow) and CWE-121 (Stack-Based Buffer Overflow) depending on where the destination buffer is allocated.

Is using strncpy() enough to prevent buffer overflow in C?

Not always. strncpy() does not guarantee null-termination when the source is longer than the limit, which can cause subsequent string operations to read past the buffer. strlcpy() is safer because it always null-terminates and returns the full source length so callers can detect truncation.

Can static analysis detect unbounded strcpy() usage in C?

Yes. Tools like Semgrep, Coverity, CodeQL, and clang-tidy all have rules that flag calls to strcpy(), gets(), and similar unbounded functions. Enabling compiler warnings (-Wdeprecated-declarations, -D_FORTIFY_SOURCE=2) also catches many of these patterns at build time.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #106

Related Articles

high

How insecure string copy functions happen in C calculations.c and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/calculations.c` at line 37, where a two-step `strncpy` + manual null-termination pattern left the door open for subtle memory safety bugs when copying string data into the `entry->type` field. The fix replaces both lines with a single `snprintf` call that handles bounds and null-termination atomically, eliminating the risk entirely. This is a common C pitfall that affects production CLI tools and can be exploited when attacker-

high

How integer truncation heap overflow happens in C++ UEFI ACPI parsing and how to fix it

A high-severity integer truncation vulnerability was discovered in `Mobility.Uefi.Acpi.cpp` where heap allocation sizes were stored in a 16-bit integer (`MO_UINT16`), causing silent truncation when the computed size exceeded 65535 bytes. This led to undersized heap allocations followed by out-of-bounds writes, exploitable by an attacker who can influence ACPI SRAT table contents in virtualized environments. The fix promotes the size variable to `MO_UINTN` (platform-native width) to prevent trunc

critical

How integer overflow in buffer size calculation happens in C and how to fix it

A critical integer overflow vulnerability was discovered in the `nsh_setvar()` function in `nshlib/nsh_vars.c`, where the buffer size calculation `newsize = pstate->varsz + varlen` could wrap around, causing a heap buffer overflow. The fix adds overflow checking before the addition, preventing attackers with shell access from corrupting memory by setting variables with crafted names and values.

high

How buffer overflow via sprintf() happens in C networking code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `profile.c` where `sprintf()` was used to format server addresses without any bounds checking. An attacker who could influence the `SERVER_BASE_PORT` value or trigger integer overflow in the port calculation could write beyond the `server_address` buffer. The fix replaces `sprintf()` with `snprintf()` using explicit buffer size limits at both call sites (lines 99 and 220).

critical

How integer overflow happens in C reliable.c and how to fix it

A critical integer overflow vulnerability was discovered in `reliable.c` at line 1299, where the `packet_buffer_size` calculation used signed `int` arithmetic that could wrap to a negative or undersized value when large `fragment_size` values were involved. By casting each operand to `size_t` before multiplication, the fix eliminates the overflow risk entirely and ensures the allocated buffer is always large enough to hold the reassembled packet data.

high

How insecure string copy functions happen in C (cyw43.c) and how to fix it

Three unsafe string copy calls in `src/cyw43.c` — including a bare `strcpy()` and two `strncpy()` calls — created buffer overflow risks in a CYW43 Wi-Fi driver emulation layer. The fix replaces all three with `snprintf()`, which enforces buffer size limits and guarantees null-termination in a single, consistent operation. Left unaddressed, these vulnerabilities could allow an attacker controlling input like a TAP interface name or SSID to corrupt adjacent memory and potentially execute arbitrary