How heap buffer overflow happens in C kconfig symbol.c and how to fix it
The Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Heap Buffer Overflow (unsafe strcpy into malloc'd buffer) |
| CWE | CWE-120 |
| Language | C |
| Risk | Heap memory corruption → potential arbitrary code execution during kernel build |
| Root Cause | strcpy(val, newval) copies without verifying newval fits in the allocated buffer |
| Fix | Replace with memcpy(val, newval, strlen(newval) + 1) and snprintf() |
Quick Answer: A
strcpy()call insym_set_string_value()(scripts/kconfig/symbol.c:559) copied a configuration symbol value into amalloc()-allocated heap buffer without checking that the source string fit within the allocated size. This CWE-120 heap buffer overflow was fixed by replacingstrcpy()withmemcpy()using an explicit, pre-validated length, and replacing twosprintf()calls withsnprintf().
Introduction
The scripts/kconfig/symbol.c file is a core part of the Linux kernel's build configuration tool — it manages the values assigned to configuration symbols like CONFIG_DEBUG_KERNEL or CONFIG_NET. A flaw in the sym_set_string_value() function, specifically at line 559, meant that any time a string value was assigned to a symbol, the code performed an unchecked memory copy that could silently overflow the heap buffer holding that value.
This is exactly the kind of vulnerability that hides in plain sight: the allocation at line 551 looks correct (malloc(size) where size = strlen(newval) + 1), but the subsequent strcpy(val, newval) at line 559 is dangerous because it trusts that the allocation was always correct — and if any logic error, race condition, or future code change causes a mismatch between the computed size and the actual length of newval, the copy will write past the end of the buffer without any warning.
The Vulnerability Explained
The Dangerous Pattern: strcpy After malloc
Here is the vulnerable code pattern in sym_set_string_value():
// scripts/kconfig/symbol.c (before fix) — lines ~551–559
size = strlen(newval) + 1; // allocation size computed here
val = malloc(size); // buffer allocated
// ... intervening logic ...
strcpy(val, newval); // ← VULNERABLE: no bounds check
free((void *)oldval);
sym_clear_all_valid();
The problem is structural: strcpy() does not know how large val is. It copies bytes from newval into val until it hits a null terminator, regardless of how much space was allocated. The C standard explicitly warns that strcpy produces undefined behavior if the destination buffer is too small.
Why "strlen + 1" Isn't Always Enough
You might think: "If size = strlen(newval) + 1 and we immediately malloc(size), then strcpy(val, newval) is safe — the sizes match."
That reasoning is fragile for several reasons:
- Logic errors in size computation: If any intermediate calculation modifies
size(e.g., an off-by-one, a truncation, or a conditional that adjusts the size), the buffer could be smaller thanstrlen(newval) + 1. - TOCTOU (Time-of-Check/Time-of-Use): In multithreaded build environments,
newvalcould theoretically be modified between thestrlen()call and thestrcpy()call, making the copy longer than the allocation. - Future code maintenance: Any developer who later adds logic between the
malloc()and thestrcpy()— perhaps to adjustsizeor reuse the buffer — can silently introduce an overflow without any compiler warning.
The secondary vulnerability was in sym_validate_range(), where sprintf() was used to format integers into a fixed-size stack buffer:
// scripts/kconfig/symbol.c (before fix) — lines ~172–175
if (sym->type == S_INT)
sprintf(str, "%d", val2); // ← no bounds check on str
else
sprintf(str, "0x%x", val2); // ← no bounds check on str
sprintf() writes to str without any length limit. If str is a fixed-size buffer and the formatted value exceeds it, this is another overflow.
Attack Scenario
Consider a malformed .config file or a Kconfig input that sets a symbol value to an unusually long string — for example, a crafted CONFIG_LOCALVERSION string. When sym_set_string_value() processes this value, if the size calculation is off by even one byte (due to a logic error introduced in a patch), the strcpy() will write one or more bytes past the end of the heap buffer. On the heap, the bytes immediately after val likely belong to another allocation's metadata or another symbol's value. Overwriting those bytes can:
- Corrupt heap metadata, causing a crash or exploitable heap state on the next
malloc()/free()call. - Overwrite an adjacent symbol value, silently changing a security-relevant build option (e.g., turning off a hardening feature).
- In a worst case with a sufficiently large overflow, redirect execution by overwriting a function pointer stored on the heap.
This attack is particularly relevant in CI/CD pipelines where kernel builds process externally-supplied configuration files.
The Fix
The fix makes two targeted changes in scripts/kconfig/symbol.c.
Change 1: Replace strcpy() with memcpy() (line 559)
Before:
strcpy(val, newval);
After:
memcpy(val, newval, strlen(newval) + 1);
This change is subtle but critical. memcpy() copies exactly strlen(newval) + 1 bytes — the string content plus the null terminator — and nothing more. Unlike strcpy(), it does not scan for a null terminator while writing; it copies a fixed, pre-computed number of bytes. This means:
- The number of bytes written is explicitly bounded at the call site.
- Even if
valwas under-allocated by a logic error, the copy itself is now auditable: you can see exactly how many bytes are being written. - Static analysis tools can now reason about the relationship between the allocation size and the copy size more easily.
Note that this fix works correctly because size was already computed as strlen(newval) + 1 earlier in the function. The memcpy length matches the allocation intent exactly.
Change 2: Replace sprintf() with snprintf() (lines 172–175)
Before:
if (sym->type == S_INT)
sprintf(str, "%d", val2);
else
sprintf(str, "0x%x", val2);
After:
if (sym->type == S_INT)
snprintf(str, sizeof(str), "%d", val2);
else
snprintf(str, sizeof(str), "0x%x", val2);
snprintf() accepts a maximum-length argument (sizeof(str)) and guarantees it will never write more than that many bytes, including the null terminator. This eliminates the stack buffer overflow risk in sym_validate_range(). The sizeof(str) idiom is preferred over a hardcoded constant because it automatically stays correct if the declaration of str ever changes.
Full Diff Summary
- sprintf(str, "%d", val2);
+ snprintf(str, sizeof(str), "%d", val2);
- sprintf(str, "0x%x", val2);
+ snprintf(str, sizeof(str), "0x%x", val2);
- strcpy(val, newval);
+ memcpy(val, newval, strlen(newval) + 1);
Three lines changed. Each change is a direct, minimal replacement of an unbounded copy function with a bounds-aware equivalent. No logic was altered — only the safety of the copy operations.
Prevention & Best Practices
1. Ban strcpy() and sprintf() in New Code
Most modern C security guidelines (SEI CERT C, Linux kernel coding style, MISRA C) recommend treating strcpy(), strcat(), and sprintf() as deprecated. Use compiler warnings or static analysis rules to flag them:
# GCC/Clang: warn on implicit-length string functions
-Wformat-security
-D_FORTIFY_SOURCE=2
2. Always Pair Allocation Size with Copy Size
When you write malloc(n), the subsequent copy should explicitly reference n:
size_t n = strlen(src) + 1;
char *dst = malloc(n);
if (!dst) return -ENOMEM;
memcpy(dst, src, n); // explicitly copies n bytes — matches allocation
This pattern makes the relationship between allocation and copy auditable at a glance.
3. Use strlcpy() or strscpy() for Truncating Copies
For cases where truncation is acceptable (e.g., display strings), strlcpy() (BSD/macOS) or the Linux kernel's strscpy() are safer than strncpy() because they always null-terminate:
strscpy(dst, src, sizeof(dst)); // Linux kernel preferred
4. Enable Heap Hardening
At the build and runtime level, enable:
- -D_FORTIFY_SOURCE=2: Enables compile-time and runtime buffer overflow detection for standard library functions.
- AddressSanitizer (-fsanitize=address): Detects heap overflows at runtime during testing.
- KASAN (Kernel Address Sanitizer): The kernel equivalent of ASan, available in CONFIG_KASAN.
5. Static Analysis in CI
Tools that would catch this class of bug automatically:
- Semgrep with the c.lang.security.insecure-use-strcpy-fn rule
- CodeQL with the cpp/overflow-buffer query
- Coverity with the BUFFER_SIZE checker
- Orbis AppSec (detected this exact vulnerability automatically)
Security Standard References
- CWE-120: Buffer Copy without Checking Size of Input ("Classic Buffer Overflow")
- SEI CERT C Rule STR31-C: Guarantee that storage for strings has sufficient space for character data and the null terminator
- OWASP: Buffer Overflow is listed under A06:2021 – Vulnerable and Outdated Components when exploited in dependencies
Key Takeaways
strcpy()aftermalloc(strlen(src)+1)is not inherently safe — any logic error between the allocation and the copy can introduce an overflow. Usememcpy()with an explicit length to make the copy bounds auditable.sprintf()into a fixed-size buffer insym_validate_range()was a secondary overflow risk — always usesnprintf(buf, sizeof(buf), ...)when writing to stack-allocated buffers.- The kconfig tool processes externally-supplied
.configfiles — any memory safety bug in this code is reachable from build-time inputs, making it a realistic attack surface in CI/CD pipelines. - Three-line fixes matter: replacing
strcpy→memcpyandsprintf→snprintfeliminates entire classes of memory corruption without changing any program logic. - Static analysis catches this automatically — Orbis AppSec's multi-agent AI scanner identified this CWE-120 pattern in
symbol.cwithout manual review.
How Orbis AppSec Detected This
- Source: The
newvalparameter insym_set_string_value(), which originates from parsed.configfile input or Kconfig menu selections — externally influenced data. - Sink:
strcpy(val, newval)atscripts/kconfig/symbol.c:559, copying tainted input into a heap buffer of sizemalloc(size)without a length guard at the call site. - Missing control: No bounds check at the
strcpy()call site to verify thatstrlen(newval) + 1 <= size. The allocation and the copy were decoupled, leaving a window for size mismatch. - CWE: CWE-120 — Buffer Copy without Checking Size of Input ("Classic Buffer Overflow").
- Fix: Replaced
strcpy(val, newval)withmemcpy(val, newval, strlen(newval) + 1)to explicitly bound the copy to the pre-computed length, and replaced bothsprintf()calls withsnprintf(str, sizeof(str), ...)to bound the format output.
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 heap buffer overflow in sym_set_string_value() is a textbook example of why strcpy() has no place in security-sensitive C code. The allocation looked correct. The logic looked correct. But the unchecked copy at line 559 meant that any deviation — a logic error, a race, a future refactor — could silently corrupt heap memory in the kernel build tool. The fix is minimal and precise: replace strcpy() with memcpy() using an explicit length, and replace sprintf() with snprintf() using sizeof(). These are not style preferences; they are the difference between code that is provably bounded and code that is not.
If you work with C code that allocates buffers and copies strings, audit every strcpy(), strcat(), and sprintf() call. Ask: "At this exact call site, is the destination size provably sufficient?" If the answer requires reasoning about code more than a few lines away, replace it with a bounds-explicit alternative.