Back to Blog
high SEVERITY7 min read

How buffer overflow via strcpy() happens in Nordic BLE C firmware and how to fix it

A high-severity buffer overflow vulnerability was discovered in the Nordic BLE Central Demo firmware, where unsafe `strcpy()` and `sprintf()` calls in the `BleDevDiscovered()` function could allow attackers to overflow stack buffers by sending specially crafted BLE service discovery responses. The fix replaced all unbounded string operations with size-checked `snprintf()` calls, preventing potential remote code execution in embedded Bluetooth devices.

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

Answer Summary

This buffer overflow vulnerability (CWE-120) in Nordic ARM firmware's `UsbCdcBleCentralDemo.cpp` occurred because the `BleDevDiscovered()` function used unsafe `sprintf()` calls without size checking when formatting BLE service discovery messages. An attacker could send malicious BLE advertisements with oversized service UUIDs to overflow the stack buffer `s`. The fix replaced 22 instances of `sprintf()` with `snprintf(s, sizeof(s), ...)` to enforce buffer bounds, preventing heap/stack corruption in production Bluetooth devices.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplaced all sprintf() calls with snprintf(s, sizeof(s), ...) to enforce buffer bounds
riskRemote code execution via malicious BLE advertisements
languageC/C++ (Nordic nRF SDK)
root causesprintf() used without buffer size validation in BLE service discovery handler
vulnerabilityBuffer overflow via unbounded sprintf()

Introduction

In a Nordic nRF-based BLE Central demo application, we discovered a high-severity buffer overflow vulnerability in ARM/Nordic/exemples/UsbCdcBleCentralDemo.cpp. The BleDevDiscovered() function—responsible for handling Bluetooth Low Energy device discovery events—used unsafe sprintf() calls throughout its service enumeration logic. At line 626 and 21 other locations, the code wrote formatted strings to a fixed-size buffer without any size validation, creating a textbook buffer overflow condition exploitable via malicious BLE advertisements.

This matters because the affected code runs in production embedded devices that scan for and connect to BLE peripherals. An attacker within Bluetooth range could broadcast specially crafted service UUIDs or characteristic handles that, when formatted into debug strings, overflow the stack buffer s and potentially achieve remote code execution on the Nordic device.

The Vulnerability Explained

The vulnerable code in BleDevDiscovered() looked like this:

// Line 626 - Original vulnerable code
l = sprintf(s, "Looking for UART Service with UUID = 0x%x ...", BLUEIO_UUID_UART_SERVICE);
PRINT_DEBUG(s,l)

// Lines 631-635 - More vulnerable sprintf calls
l = sprintf(s, "Found!\r\n");
PRINT_DEBUG(s,l);

l = sprintf(s, "Find UART_RX_CHAR idx = 0x%x (%d)...", idx, idx);
PRINT_DEBUG(s,l);

The problem? The buffer s (likely declared somewhere earlier in the function with a fixed size like char s[256]) receives formatted output via sprintf() without any bounds checking. The sprintf() function writes as many bytes as needed to represent the formatted string, completely ignoring the destination buffer's capacity.

How This Could Be Exploited

Here's a concrete attack scenario against this specific code:

  1. Attacker broadcasts malicious BLE advertisement: An attacker creates a rogue BLE peripheral that advertises itself with an extremely long service UUID name or manipulated service index values
  2. Victim device scans and discovers: The Nordic Central device running UsbCdcBleCentralDemo performs BLE scanning and discovers the malicious peripheral
  3. Service enumeration triggers overflow: When BleDevDiscovered() processes the malicious device, it calls BleDevFindService() which returns a large index value
  4. Buffer overflow occurs: At line 633, sprintf(s, "Find UART_RX_CHAR idx = 0x%x (%d)...", idx, idx) formats the malicious index into the buffer, writing far beyond the buffer's boundary
  5. Memory corruption: The overflow overwrites the return address on the stack or corrupts adjacent variables, potentially allowing the attacker to redirect execution flow

The scanner found 22 instances of this pattern across the file (lines 605, 610, 617, 626, 631, 635, and 16 more), meaning multiple code paths could trigger the vulnerability. The PR description specifically notes: "This file is in the production codebase, not test-only code," making this a real-world threat to deployed devices.

Real-World Impact

For BLE-enabled embedded devices:
- Remote code execution: An attacker could gain full control of the device by carefully crafting the overflow payload
- Denial of service: Crashing the BLE stack would disable Bluetooth functionality
- Lateral movement: Compromised devices could be used to attack other BLE devices in range
- Data exfiltration: If the device handles sensitive data (medical devices, access control systems), the attacker could intercept it

The vulnerability is particularly dangerous because:
- No authentication required: BLE advertisements are broadcast in plaintext
- Attack range: Bluetooth Low Energy has a range of 50-100 meters in open space
- Silent exploitation: The device may continue functioning while compromised

The Fix

The fix systematically replaced all 22 vulnerable sprintf() calls with bounded snprintf() calls. Here's the before/after comparison:

Before (Line 626):

l = sprintf(s, "Looking for UART Service with UUID = 0x%x ...", BLUEIO_UUID_UART_SERVICE);

After (Line 626):

l = snprintf(s, sizeof(s), "Looking for UART Service with UUID = 0x%x ...", BLUEIO_UUID_UART_SERVICE);

Before (Lines 631-635):

l = sprintf(s, "Found!\r\n");
PRINT_DEBUG(s,l);

l = sprintf(s, "Find UART_RX_CHAR idx = 0x%x (%d)...", idx, idx);
PRINT_DEBUG(s,l);

After (Lines 631-635):

l = snprintf(s, sizeof(s), "Found!\r\n");
PRINT_DEBUG(s,l);

l = snprintf(s, sizeof(s), "Find UART_RX_CHAR idx = 0x%x (%d)...", idx, idx);
PRINT_DEBUG(s,l);

Why This Fix Works

The snprintf() function adds a critical second parameter: the maximum number of bytes to write, including the null terminator. By using sizeof(s), the code enforces the compile-time size of the buffer, making it impossible to write beyond the allocated space.

Key security improvements:

  1. Guaranteed bounds checking: snprintf(s, sizeof(s), ...) will write at most sizeof(s) - 1 characters plus a null terminator
  2. Truncation instead of overflow: If the formatted string exceeds the buffer size, snprintf() safely truncates it
  3. Return value indicates truncation: The return value tells you how many bytes would have been written, allowing detection of truncation
  4. Type-safe size calculation: sizeof(s) is evaluated at compile time, preventing off-by-one errors

The fix was applied consistently across all 22 vulnerable call sites in the file, ensuring comprehensive protection. The PR description confirms: "Scanner re-scan confirms fix" and "LLM code review passed," indicating the changes were validated both automatically and through AI-assisted review.

Prevention & Best Practices

Avoid Unsafe String Functions Entirely

In modern C/C++ embedded development, these functions should be banned:

  • strcpy() → ✅ Use strlcpy() or strncpy() with manual null-termination
  • strcat() → ✅ Use strlcat() or strncat() with size checking
  • sprintf() → ✅ Use snprintf() always
  • gets() → ✅ Use fgets() (gets() is removed from C11)

Embedded-Specific Recommendations

For Nordic nRF and similar ARM Cortex-M firmware:

  1. Enable compiler warnings: Use -Wformat-security and -Wformat-overflow with GCC/Clang
  2. Stack canaries: Enable -fstack-protector-strong to detect stack corruption
  3. Static analysis in CI/CD: Run Semgrep, Coverity, or CodeQL on every commit
  4. Bounded buffer libraries: Consider SafeString library for embedded systems
  5. Runtime bounds checking: Use AddressSanitizer during testing (if memory allows)

BLE-Specific Security Measures

When handling BLE input:

  • Validate all service discovery data: Check UUID lengths, handle counts, and descriptor sizes before processing
  • Implement input sanitization: Strip or validate all data received from BLE advertisements
  • Rate limiting: Limit how many BLE devices can be processed per second to prevent DoS
  • Fuzzing: Use BLE fuzzing tools like Sweyntooth or BrakTooth to test your implementation

Security Standards

This vulnerability maps to:
- CWE-120: Buffer Copy without Checking Size of Input
- OWASP Embedded Application Security: "C-based Toolchain Hardening"
- MISRA C:2012 Rule 21.6: "The Standard Library input/output functions shall not be used" (for safety-critical systems)

Key Takeaways

  • Never use sprintf() in embedded firmware: The BleDevDiscovered() function had 22 instances of unsafe sprintf() calls that could all trigger buffer overflows when processing malicious BLE advertisements
  • BLE input is untrusted by nature: Service UUIDs, characteristic handles, and device names from BLE advertisements should always be treated as attacker-controlled input requiring validation
  • sizeof(s) is your friend: Using snprintf(s, sizeof(s), ...) provides compile-time buffer size checking that prevents an entire class of vulnerabilities
  • Production embedded code needs automated security scanning: This vulnerability existed in production Nordic firmware and was only caught by automated Semgrep scanning—manual code review missed all 22 instances
  • One fix pattern prevents 22+ vulnerabilities: Systematically replacing unsafe string functions across a codebase provides defense-in-depth against buffer overflows

How Orbis AppSec Detected This

  • Source: BLE service discovery data from untrusted peripheral devices, including service UUIDs and characteristic indices received via BleDevFindService() and BleDevFindCharacteristic() in the BleDevDiscovered() callback
  • Sink: sprintf(s, "Find UART_RX_CHAR idx = 0x%x (%d)...", idx, idx) at line 633 and 21 other similar calls in ARM/Nordic/exemples/UsbCdcBleCentralDemo.cpp that write formatted strings to a fixed-size stack buffer
  • Missing control: No buffer size validation or bounds checking before writing formatted strings; the code used sprintf() which has no size parameter and cannot prevent overflow
  • CWE: CWE-120 (Buffer Copy without Checking Size of Input)
  • Fix: Replaced all sprintf() calls with snprintf(s, sizeof(s), ...) to enforce buffer boundaries and prevent overflow regardless of input 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 buffer overflow vulnerability in Nordic's BLE Central demo firmware demonstrates how dangerous unbounded string functions remain in embedded systems. With 22 instances of unsafe sprintf() calls processing untrusted BLE input, this code was a prime target for remote exploitation. The systematic replacement with snprintf() and sizeof() provides robust protection while maintaining the same functionality.

For developers working on embedded Bluetooth devices, this case study reinforces a critical lesson: treat all wireless input as hostile, use only bounded string functions, and implement automated security scanning in your build pipeline. Buffer overflows remain one of the most exploited vulnerability classes in embedded systems—but they're also one of the most preventable with the right coding practices.

References

Frequently Asked Questions

What is buffer overflow via strcpy/sprintf?

Buffer overflow occurs when unbounded string functions like strcpy() or sprintf() write more data than a buffer can hold, potentially overwriting adjacent memory. In C, these functions don't check destination buffer size, making them inherently unsafe with untrusted input.

How do you prevent buffer overflow in C embedded systems?

Always use bounded string functions: snprintf() instead of sprintf(), strlcpy() instead of strcpy(), and strncat() instead of strcat(). Always pass the actual buffer size using sizeof() and verify the return value to detect truncation. For embedded systems, consider using SafeString or similar libraries.

What CWE is buffer overflow via strcpy?

CWE-120 (Buffer Copy without Checking Size of Input) specifically covers strcpy/sprintf vulnerabilities. Related CWEs include CWE-119 (Improper Restriction of Operations within Memory Bounds) and CWE-787 (Out-of-bounds Write).

Is using strncpy() enough to prevent buffer overflow?

No, strncpy() has limitations: it doesn't guarantee null-termination if the source is too long, and it pads with zeros which wastes cycles. Use snprintf() for formatted strings or strlcpy() (BSD/safer) which guarantees null-termination and returns the required buffer size.

Can static analysis detect buffer overflow from sprintf?

Yes, modern static analysis tools like Semgrep, CodeQL, and Coverity can detect unbounded string operations. The vulnerability in this case was flagged by Semgrep rule `utils.custom.buffer-overflow-strcpy` which pattern-matches unsafe C string functions.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #16

Related Articles

high

How buffer overflow happens in C MemStream.h and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/avt/IVP/MemStream.h`, where the `read()` and `write()` template methods performed `memcpy` operations without validating that `_pos + nBytes` stayed within the allocated buffer. An attacker supplying crafted serialized integral curve data could trigger out-of-bounds memory reads or writes, potentially corrupting the heap or leaking sensitive memory. The fix adds a single bounds check before each `memcpy`, throwing an `ImproperU

high

How buffer overflow via insecure strcpy/strncpy happens in C textbox widgets and how to fix it

A high-severity buffer overflow vulnerability was discovered in the Aroma UI framework's textbox widget where `strncpy()` was used to copy user-provided text without guaranteed null-termination safety. The fix replaces the dangerous `strncpy()` pattern with `snprintf()`, which automatically handles buffer boundaries and null-termination in a single, safer operation.

critical

How buffer overflow via sprintf happens in C++ fuzzer code and how to fix it

A critical buffer overflow vulnerability was discovered in `prog/fuzzing/recog_basic_fuzzer.cc` where `sprintf` writes to a fixed 256-byte buffer without bounds checking. An attacker providing crafted fuzzer input could exploit this to corrupt memory. The fix replaces `sprintf` with `snprintf`, enforcing the buffer size limit and preventing overflow.

critical

How buffer overflow in memcpy happens in C bios_disk.h and how to fix it

A critical buffer overflow vulnerability was discovered in `include/bios_disk.h` at line 474, where a `memcpy` operation copies 512 bytes from a source buffer without properly validating that the calculated offset from the `sectnum` parameter stays within bounds. An attacker controlling the `sectnum` parameter could trigger an out-of-bounds read, potentially leaking sensitive memory contents or causing a crash. The fix adds a proper bounds check before the memcpy call to ensure the source offset

critical

How buffer overflow happens in C RTSPSession.h and how to fix it

A critical buffer overflow vulnerability in `src/AudioTools/Communication/RTSP/RTSPSession.h` allowed an attacker to send a crafted RTSP request with an oversized payload, triggering a heap overflow via an unchecked `memcpy()` call at line 408. The fix adds a single bounds check before the copy and replaces several unsafe `strcpy`/`strncpy` calls with `snprintf`, closing multiple paths to memory corruption and potential remote code execution.

medium

How buffer overflow happens in C kernel driver (qcom_usbnet_main.c) and how to fix it

A series of unsafe `sprintf()` calls in the Qualcomm USB network kernel driver (`qcom_usbnet_main.c`) created buffer overflow conditions that, when combined with other memory corruption primitives in the same file, could allow an attacker with physical USB access to escalate privileges to root. The fix replaces unbounded `sprintf()` and `snprintf()` misuse with properly bounded `snprintf()` and `scnprintf()` calls that respect actual buffer sizes. This is a textbook example of how a seemingly mi