Back to Blog
critical SEVERITY6 min read

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

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

Answer Summary

This is a buffer overflow vulnerability (CWE-120) in C where the `memcpy` operation in `include/bios_disk.h:474` copies 512 bytes from a source buffer using an attacker-controllable `sectnum` parameter without validating that the computed offset stays within the buffer's bounds. The fix adds an explicit bounds check that verifies `(sectnum * 512) + 512 <= buffer_size` before performing the copy, preventing out-of-bounds reads and potential memory disclosure.

Vulnerability at a Glance

cweCWE-120
fixAdd bounds check verifying offset + copy size ≤ buffer size before memcpy
riskOut-of-bounds read leading to memory disclosure or crash
languageC
root causeMissing validation that sectnum-derived offset plus 512 bytes stays within source buffer bounds
vulnerabilityBuffer overflow via unchecked memcpy

How Buffer Overflow in memcpy Happens in C bios_disk.h and How to Fix It

Introduction

In the DOSBox-X codebase, a critical buffer overflow vulnerability was discovered in include/bios_disk.h at line 474. The bios_disk_read function uses memcpy to copy 512 bytes (one disk sector) from a source buffer into a destination data buffer, but the offset calculation derived from the sectnum parameter lacked sufficient validation. While the existing code checked whether the offset exceeded the buffer size, it failed to verify that the offset plus the 512-byte copy length remained within bounds — a classic off-by-one category error that left the door open to out-of-bounds memory reads.

This vulnerability is particularly dangerous because sectnum can be influenced by disk I/O requests, meaning an attacker with the ability to craft malicious disk images or manipulate sector parameters could trigger memory disclosure or denial of service.

The Vulnerability Explained

The vulnerable code in include/bios_disk.h:474 performs a sector read operation that looks conceptually like this:

// Vulnerable code (before fix)
uint32_t offset = sectnum * 512;
// Insufficient check: only validates offset, not offset + copy_size
if (offset >= buffer_size) {
    return error_code;
}
memcpy(data, buffer + offset, 512);

The problem is subtle but critical. Consider what happens when sectnum is at the boundary:

  • Buffer size: 2048 bytes
  • sectnum = 4 → offset = 2048
  • The check offset >= buffer_size catches this case (2048 >= 2048 is true) ✓
  • But sectnum = 3 → offset = 1536
  • The check 1536 >= 2048 is false, so memcpy proceeds
  • memcpy(data, buffer + 1536, 512) reads bytes 1536–2047 ✓

Now consider a larger buffer scenario or integer overflow:

  • If sectnum = 0xFFFFFFFF, the multiplication sectnum * 512 overflows a 32-bit integer
  • The resulting offset wraps around to a small value
  • The bounds check passes because the wrapped value is less than buffer_size
  • memcpy then reads from an arbitrary memory location relative to buffer

Attack Scenario: An attacker who can control the sectnum parameter — for example, by providing a crafted disk image with manipulated partition table entries or by exploiting a BIOS interrupt handler — can cause the emulator to:

  1. Read out-of-bounds memory: Leaking adjacent heap or stack data that may contain sensitive information
  2. Crash the application: Accessing unmapped memory pages triggers a segmentation fault
  3. Bypass ASLR: Leaked memory addresses from adjacent allocations can defeat address space layout randomization

The regression test included in the PR demonstrates these exact scenarios:

uint32_t test_cases[] = {
    0xFFFFFFFF,  // Exploit: large sectnum causing overflow
    0x00000004,  // Boundary: exactly at buffer limit
    0x00000000   // Valid: within bounds
};

The Fix

The fix adds a proper bounds check that validates both the offset calculation and the total read range before the memcpy operation executes:

Before (vulnerable):

uint32_t offset = sectnum * 512;
if (offset >= buffer_size) {
    return error_code;
}
memcpy(data, buffer + offset, 512);

After (fixed):

uint32_t offset = sectnum * 512;
// Check for integer overflow in multiplication
if (sectnum != 0 && offset / sectnum != 512) {
    return error_code;
}
// Check that offset + copy size stays within buffer bounds
if (offset + 512 > buffer_size) {
    return error_code;
}
memcpy(data, buffer + offset, 512);

The fix addresses two distinct issues:

  1. Integer overflow protection: By verifying that offset / sectnum == 512, the code detects when the multiplication has wrapped around. This prevents attackers from using large sectnum values to bypass the subsequent bounds check.

  2. Complete bounds validation: The check offset + 512 > buffer_size ensures that the entire 512-byte read window falls within the buffer, not just the starting offset. This closes the gap where the old check would allow reads that started within bounds but extended past the buffer's end.

The PR also disabled a potentially problematic CI step (if: false on the Windows build run step) and updated actions/checkout from v6 to v7 across multiple workflow files, which are maintenance changes unrelated to the security fix but included in the same PR for CI stability.

Prevention & Best Practices

1. Always validate the full range, not just the start

When performing buffer copies with computed offsets, check that offset + length <= buffer_size. Never assume that a valid starting offset implies a valid read range.

2. Guard against integer overflow in offset calculations

In C, unsigned integer overflow is defined behavior (it wraps), but it's almost never what you want. Use overflow-safe arithmetic:

// Safe multiplication check
if (sectnum > (SIZE_MAX / 512)) {
    return error_code;  // Would overflow
}

3. Use size_t for buffer arithmetic

Using uint32_t for offset calculations on 64-bit systems can mask overflows. Prefer size_t which matches the platform's address space.

4. Consider using safe memory copy wrappers

Create a project-wide safe_memcpy function that takes source buffer size as a parameter and validates bounds internally:

int safe_memcpy(void *dst, size_t dst_size, const void *src, 
                size_t src_size, size_t offset, size_t count) {
    if (offset + count > src_size || count > dst_size) return -1;
    memcpy(dst, (const char*)src + offset, count);
    return 0;
}

5. Static analysis integration

Run tools like Coverity, PVS-Studio, or Semgrep in CI to catch unchecked memcpy patterns before they reach production.

Key Takeaways

  • The sectnum * 512 calculation in bios_disk.h:474 could overflow a 32-bit integer, wrapping around to bypass the existing bounds check — always validate arithmetic before using the result.
  • Checking only the offset without the copy length is insufficient — the old check offset >= buffer_size missed cases where the read window extended past the buffer end.
  • Disk I/O emulation code is security-critical because sector numbers originate from potentially untrusted disk images that users load.
  • Line 475 in the same file uses a similar pattern and should be reviewed with the same bounds-checking approach.
  • The regression test with sectnum = 0xFFFFFFFF demonstrates that integer overflow is the primary exploitation vector, not just large-but-valid sector numbers.

How Orbis AppSec Detected This

  • Source: The sectnum parameter passed to the BIOS disk read function, which originates from disk image metadata or emulated BIOS interrupt calls
  • Sink: memcpy(data, buffer + offset, 512) in include/bios_disk.h:474
  • Missing control: No validation that (sectnum * 512) + 512 stays within the source buffer bounds, and no integer overflow check on the multiplication
  • CWE: CWE-120 (Buffer Copy without Checking Size of Input)
  • Fix: Added bounds check verifying that the computed offset plus 512 bytes does not exceed the buffer size, with integer overflow detection on the sector-to-offset multiplication

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 overflows in memcpy operations remain one of the most dangerous vulnerability classes in C code. This specific instance in bios_disk.h demonstrates how a seemingly reasonable bounds check can be insufficient when it doesn't account for the full copy range or integer overflow in offset calculations. The fix is straightforward — validate the complete read window and check for arithmetic overflow — but the consequences of missing it are severe: memory disclosure, crashes, and potential code execution. When writing low-level buffer manipulation code, always ask: "Does my check cover the entire operation, not just the starting point?"

References

Frequently Asked Questions

What is a buffer overflow in memcpy?

A buffer overflow in memcpy occurs when the function copies data beyond the allocated boundaries of either the source or destination buffer, typically because the offset or size parameters are not properly validated against actual buffer dimensions.

How do you prevent buffer overflow in C memcpy calls?

Always validate that the source offset plus the number of bytes to copy does not exceed the source buffer size, and that the destination buffer is large enough to hold the copied data. Use explicit bounds checks immediately before every memcpy call.

What CWE is buffer overflow?

Buffer overflow is classified as CWE-120 (Buffer Copy without Checking Size of Input), which is part of the broader CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer) family.

Is checking only the offset enough to prevent buffer overflow in memcpy?

No. You must check that the offset plus the number of bytes being copied does not exceed the buffer size. Checking only the offset without accounting for the copy length still allows reading or writing past the buffer boundary.

Can static analysis detect buffer overflow in memcpy?

Yes. Static analysis tools like Coverity, PVS-Studio, and Semgrep can detect missing bounds checks before memcpy calls, especially when the offset is derived from user-controllable input like sector numbers or indices.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6371

Related Articles

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.

high

How unbounded input size denial-of-service happens in C lexer functions and how to fix it

A high-severity denial-of-service vulnerability was discovered in the PH7 lexer where the `PH7_TokenizePHP()` function accepted arbitrarily large input sizes without validation. An attacker could submit gigabyte-scale PHP code, causing proportional CPU and memory exhaustion. The fix introduces a configurable input size cap enforced before lexer processing begins.

critical

How command injection happens in Python subprocess and how to fix it

A critical command injection vulnerability was discovered in `script/llm_semantic_analyzer.py` at line 394, where user-controlled input (API keys and model parameters) was interpolated directly into shell commands passed to `subprocess.run` with `shell=True`. An attacker who could control these parameters could inject shell metacharacters like `; rm -rf /` or `$(whoami)` to execute arbitrary commands. The fix sanitizes all user input before it reaches shell execution.

critical

How path traversal happens in Python os.path and how to fix it

A critical path traversal vulnerability in the TRL backend allowed attackers to read arbitrary system files like `/etc/passwd` and `/proc/self/environ` through the gRPC fine-tuning API. The `_do_training` method passed user-controlled `dataset_source` directly to `os.path.exists()` and `load_dataset()` without validation. The fix implements strict directory containment checks using `os.path.realpath()` to ensure all file operations stay within allowed directories.

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.