Back to Blog
critical SEVERITY7 min read

How buffer overflow happens in C gdb-server and how to fix it

A critical buffer overflow vulnerability was discovered in `src/st-util/gdb-server.c` where unbounded `memcpy()` and `strcpy()` calls could write beyond allocated buffer boundaries when processing user-supplied command-line arguments. The fix replaces all unsafe string operations with bounds-checked alternatives like `snprintf()` and `memcpy()` with explicit length validation.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published July 3, 2026•Reviewed July 3, 2026

Answer Summary

This vulnerability is a classic buffer overflow (CWE-120) in C caused by unbounded string operations (`strcpy()`, `memcpy()` without length checks) in the st-util GDB server. The vulnerability exists in `gdb-server.c` at lines 212, 367, 376, and 994 where user input or template strings are copied into fixed-size buffers without validation. The fix replaces all unsafe operations with `snprintf()` (which enforces buffer boundaries) and proper `memcpy()` with explicit size limits, ensuring no writes exceed allocated buffer space.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplace strcpy()/memcpy() with snprintf() and bounded memcpy() calls that enforce STLINK_SERIAL_BUFFER_SIZE limits
riskHeap corruption, denial of service, potential code execution via stack/heap overflow
languageC
root causeUsing strcpy() and memcpy() with user-controlled or template data without validating that the source fits in the destination buffer
vulnerabilityBuffer overflow via unbounded strcpy() and memcpy()

How Buffer Overflow Happens in C gdb-server and How to Fix It

Introduction

In the st-util GDB server component (src/st-util/gdb-server.c), a critical buffer overflow vulnerability was discovered that could allow an attacker to corrupt heap memory by supplying a maliciously long --serial command-line argument. The vulnerability stems from four locations in the code where unbounded string operations—strcpy() and memcpy()—copy data into fixed-size buffers without checking if the source fits. Specifically:

  • Line 212: memcpy(st->serialnumber, optarg, STLINK_SERIAL_BUFFER_SIZE) incorrectly assumes optarg is smaller than the buffer size
  • Line 367 & 376: Two strcpy() calls copy template strings into a dynamically allocated map buffer without size validation
  • Line 994: strncpy() is used but lacks null-termination guarantee

This is a classic memory safety issue in C that affects any developer working with command-line tools, embedded systems, or low-level utilities. Understanding this vulnerability and its fix is essential for writing secure C code.


The Vulnerability Explained

The Problematic Code

Let's examine the vulnerable code at line 212 in the parse_options() function:

case SERIAL_OPTION:
    printf("use serial %s\n", optarg);
    memcpy(st->serialnumber, optarg, STLINK_SERIAL_BUFFER_SIZE);
    break;

The issue is subtle but dangerous: the code uses memcpy() with STLINK_SERIAL_BUFFER_SIZE as the copy length, but STLINK_SERIAL_BUFFER_SIZE defines the destination buffer size, not the source length. The optarg pointer comes directly from the command-line argument parser and has no length validation.

If an attacker runs:

./st-util --serial AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

with a serial string longer than STLINK_SERIAL_BUFFER_SIZE (typically 16 bytes), the memcpy() call will write beyond the st->serialnumber buffer boundary, corrupting adjacent heap memory.

Similar Issues in Memory Map Functions

The same pattern appears in the make_memory_map() function at lines 367 and 376:

} else if(sl->chip_id == STM32_CHIPID_F4_DE) {
    strcpy(map, memory_map_template_F4_DE);  // Line 367 - DANGEROUS!
} else if(sl->chip_id == STM32_CHIPID_F4_HD) {
    strcpy(map, memory_map_template_F4_HD);  // Line 376 - DANGEROUS!

Here, strcpy() copies a template string into the map buffer. While map is dynamically allocated with calloc(), the allocation size is computed earlier in the function. If the template string is larger than the allocated size (a possible scenario in firmware updates or configuration changes), the strcpy() will overflow.

Attack Scenario

An attacker who can control the command-line arguments to st-util can trigger this vulnerability:

# Craft a serial number exceeding STLINK_SERIAL_BUFFER_SIZE
./st-util --serial "$(python3 -c 'print("A" * 100)')"

The memcpy() call writes 16 bytes from a 100-byte string, but the destination buffer is only 16 bytes. The extra 84 bytes corrupt:
- Adjacent heap metadata
- Other heap-allocated structures
- Potentially enabling a heap exploit or denial of service

Real-World Impact

For a tool like st-util (used for STM32 microcontroller debugging), this vulnerability could:
- Crash the debugger during firmware upload/download operations
- Corrupt debug session state, making debugging unreliable
- Escalate privileges if st-util runs with elevated permissions (less common but possible in embedded development environments)
- Enable remote code execution if the heap corruption is leveraged with a heap spray attack


The Fix

What Changed

The fix replaces all four unsafe string operations with bounds-checked alternatives:

Fix 1: Line 212 - Replace memcpy with snprintf

Before:

memcpy(st->serialnumber, optarg, STLINK_SERIAL_BUFFER_SIZE);

After:

snprintf(st->serialnumber, STLINK_SERIAL_BUFFER_SIZE, "%s", optarg);

Why this works: snprintf() enforces the buffer size limit (second parameter) and automatically null-terminates the string. Even if optarg is 1000 bytes, snprintf() will copy at most STLINK_SERIAL_BUFFER_SIZE - 1 bytes and add a null terminator. The string is safely truncated, not overflowed.

Fix 2 & 3: Lines 367 & 376 - Replace strcpy with snprintf

Before (Line 367):

strcpy(map, memory_map_template_F4_DE);

After:

snprintf(map, sz, "%s", memory_map_template_F4_DE);

Why this works: The variable sz holds the allocated size of map. By passing sz to snprintf(), we ensure the template string cannot exceed the buffer boundary. If the template is larger than sz, it's truncated safely.

Fix 4: Line 994 - Replace strncpy with memcpy + explicit size

Before:

strncpy(queryName, &packet[1], queryNameLength);

After:

memcpy(queryName, &packet[1], queryNameLength);

Why this works: The original code used strncpy(), which doesn't guarantee null-termination if the source is exactly queryNameLength bytes. The buffer queryName is allocated with calloc(1, queryNameLength + 1), which zeros memory. Using memcpy() with the exact size and relying on the zero-initialized buffer is safe and more efficient.

Security Improvement

The fixes enforce a security invariant: no string operation will write beyond its destination buffer boundary, even with adversarial input.

Aspect Before After
Buffer boundary check None Enforced by snprintf()
Null termination Assumed Guaranteed
Truncation behavior Overflow Safe truncation
Input validation None Implicit in function

Key Takeaways

  • Never copy user-controlled data with unbounded functions. The --serial argument in line 212 is attacker-controlled; memcpy() with no length check is a direct buffer overflow.

  • strcpy() is unsafe in all contexts. Even if you "know" the string is small, future code changes or configuration updates can introduce longer strings. Use snprintf() instead.

  • snprintf() is your friend. It enforces buffer boundaries, null-terminates automatically, and safely truncates. It's the standard way to format and copy strings in modern C.

  • Template strings must be validated against buffer size. The memory map templates in lines 367 and 376 are "internal" strings, but they can still overflow if the buffer size calculation is wrong. Always use bounded functions.

  • Compiler protections catch what code review misses. Stack canaries and AddressSanitizer would have detected this vulnerability during testing. Use them in development and CI/CD.


How Orbis AppSec Detected This

Source: Command-line argument optarg from getopt() parser, passed directly to memcpy() without length validation.

Sink:
- memcpy(st->serialnumber, optarg, STLINK_SERIAL_BUFFER_SIZE) at line 212
- strcpy(map, memory_map_template_F4_DE) at line 367
- strcpy(map, memory_map_template_F4_HD) at line 376
- strncpy(queryName, &packet[1], queryNameLength) at line 994

Missing Control: No validation that source data length fits within destination buffer size. No use of bounded string functions.

CWE: CWE-120 – Buffer Copy without Checking Size of Input

Fix: Replaced strcpy() and memcpy() with snprintf() (which enforces the buffer size parameter) and corrected strncpy() to use memcpy() with explicit size validation from buffer allocation.

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

Buffer overflow vulnerabilities in C are among the most critical security issues, with a history spanning decades of exploits and breaches. The fix in st-util demonstrates a fundamental principle: use bounded functions by default, never unbounded ones.

The shift from strcpy() to snprintf(), from memcpy() without checks to memcpy() with validated sizes, and from strncpy() without null-termination guarantees to safer alternatives represents a maturation of secure C coding practices.

If you maintain C code, especially low-level tools like debuggers, embedded systems, or security-critical applications:

  1. Audit your codebase for strcpy(), sprintf(), and unbounded memcpy() calls
  2. Replace them with snprintf(), strlcpy(), or bounded alternatives
  3. Enable compiler protections during development and testing
  4. Use static analysis in your CI/CD pipeline to catch new issues before they reach production

The developers of st-util made the right choice in fixing this issue proactively. Your code can be just as secure.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1495

Related Articles

critical

Aardvark.Cef.Process.Core Shared Memory Handler: Unvalidated memcpy

The shared memory handler in Aardvark.Cef.Process.Core failed to validate that the `length` parameter passed from JavaScript did not exceed the destination buffer size before calling `memcpy`. This allowed a compromised Chromium renderer process to corrupt heap memory by supplying an oversized `handle->length` value to the `openMapping` function. The fix adds explicit bounds checking using `GetArrayBufferByteLength()` before the copy operation.

high

XShmGetImage Heap Corruption: Unvalidated Image Height in streamproxy

The `XShmGetImage` function in the X11 shared memory image path copies pixel data row-by-row using `memcpy` without validating that the source image height matches the destination buffer height. An attacker or compromised server could provide an oversized source image, causing writes beyond the allocated heap buffer and triggering heap corruption or code execution.

critical

SGX Enclave ecall_store_data memcpy Buffer Overflow in 256-Byte

The Intel SGX enclave's trusted bridge functions `ecall_store_data` and `ecall_retrieve_data` used `memcpy()` to move data into and out of a fixed 256-byte `secure_storage` buffer without validating that `data_len` fit within destination boundaries. An attacker providing oversized `data_len` values could corrupt enclave memory, breaking SGX's confidentiality guarantees.

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.