Back to Blog
critical SEVERITY9 min read

How buffer overflow happens in C dcraw_lz.c nikon_3700() and how to fix it

A critical buffer overflow vulnerability was discovered in `lightcrafts/coprocesses/dcraw/dcraw_lz.c` at line 1334, where the `nikon_3700()` function used `strcpy()` to copy camera make and model strings into fixed 64-byte buffers without any bounds checking. A crafted RAW image file with oversized make/model metadata could trigger a heap or stack corruption, potentially enabling arbitrary code execution. The fix replaces both `strcpy()` calls with `strncpy()` and explicit null-termination, enfo

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

Answer Summary

This is a CWE-120 buffer overflow vulnerability in C, found in the `nikon_3700()` function of `lightcrafts/coprocesses/dcraw/dcraw_lz.c`. The root cause is two unchecked `strcpy()` calls that copy camera make/model strings from a lookup table into fixed 64-byte buffers — if the source strings exceed 64 bytes, adjacent memory is overwritten. The fix replaces `strcpy()` with `strncpy(..., sizeof buf - 1)` followed by explicit null-termination, ensuring writes never exceed the declared buffer size regardless of input length.

Vulnerability at a Glance

cweCWE-120
fixReplace strcpy() with strncpy() bounded to sizeof(buffer)-1, followed by explicit null-termination
riskCrafted RAW image file triggers memory corruption, potentially enabling arbitrary code execution
languageC
root causestrcpy() copies make/model strings into 64-byte buffers without checking source length
vulnerabilityBuffer Overflow (unbounded strcpy into fixed-size buffer)

How buffer overflow happens in C dcraw_lz.c nikon_3700() and how to fix it

Vulnerability at a glance: CWE-120 buffer overflow in nikon_3700() — two unchecked strcpy() calls copy camera metadata into 64-byte buffers. Fixed by replacing with bounds-checked strncpy() plus explicit null-termination.


Summary

A critical buffer overflow vulnerability was found in lightcrafts/coprocesses/dcraw/dcraw_lz.c at line 1334. The nikon_3700() function used strcpy() to copy camera make and model strings from a lookup table into fixed-size 64-byte character arrays — with zero bounds checking. A crafted RAW image file containing oversized make/model metadata fields could trigger memory corruption, potentially leading to arbitrary code execution. The fix replaces both strcpy() calls with strncpy() bounded to the buffer size, followed by explicit null-termination.


Introduction

The lightcrafts/coprocesses/dcraw/dcraw_lz.c file is a C implementation of the widely-used dcraw RAW image decoder, responsible for parsing and demosaicing RAW photographs from digital cameras. Deep inside this decoder, the nikon_3700() function handles a quirk specific to early Nikon 3700-era cameras: it reads a bitmask from the image's binary data, looks it up in a hardcoded table of known camera configurations, and copies the matching make and model strings into global character buffers.

That copy operation — two innocuous-looking lines — contained a critical flaw:

strcpy (make,  table[i].make );
strcpy (model, table[i].model);

The make and model buffers are fixed-size arrays (64 bytes each, as is standard in dcraw). strcpy() doesn't know or care about that limit. If any string in the lookup table — or more dangerously, any string injected via a crafted RAW file — exceeds 63 characters, bytes spill past the buffer boundary and overwrite adjacent memory.


The Vulnerability Explained

What strcpy() Actually Does

strcpy(dest, src) copies bytes from src to dest, one at a time, until it encounters a null byte (\0). It performs no length check. The C standard explicitly states this is undefined behavior when the source is longer than the destination — but undefined behavior in C often means "overwrite whatever is next in memory."

The Specific Vulnerable Code

Here is the exact vulnerable code from nikon_3700() at line 1334:

// VULNERABLE - no bounds checking
void CLASS nikon_3700()
{
  // ...
  for (i=0; i < sizeof table / sizeof *table; i++)
    if (bits == table[i].bits) {
      strcpy (make,  table[i].make );   // ← CWE-120: no length check
      strcpy (model, table[i].model);   // ← CWE-120: no length check
    }
}

The make and model variables are char arrays declared elsewhere in dcraw, typically as:

char make[64], model[64];

That means strcpy() is writing into a 64-byte slot. If table[i].make or table[i].model contains a string of 64 characters or more (including the null terminator, that means anything ≥ 64 characters in length), the write overflows the buffer.

How Could This Be Exploited?

dcraw processes RAW image files — binary files whose metadata fields, including camera make and model, are read from disk and parsed. An attacker who can supply a crafted RAW file to an application using this library has direct influence over the content that flows into these strcpy() calls.

Attack scenario:

  1. An attacker crafts a Nikon RAW file whose embedded metadata causes bits to match one of the entries in table[].
  2. The corresponding table[i].make or table[i].model string is longer than 63 bytes.
  3. strcpy() faithfully copies all of it, writing past the end of make[64] or model[64].
  4. Depending on memory layout (stack vs. heap), this overwrites adjacent variables, return addresses, or heap metadata.
  5. With careful crafting, an attacker can redirect execution — achieving arbitrary code execution in the context of the application processing the image.

Applications that batch-process user-uploaded RAW files (photo editors, media servers, cloud photo libraries) are particularly exposed, since they process attacker-controlled files without user interaction.

Real-World Impact for This Component

LightZone, the application containing this code, is a photo editing tool that processes RAW files from a wide variety of cameras. Any workflow that opens untrusted RAW files — including plugins, batch importers, or web-facing upload endpoints — could be exploited via this path. Because dcraw is a widely forked library, variants of this vulnerability exist in many image-processing codebases.


The Fix

The fix is surgical and correct. Both strcpy() calls are replaced with strncpy() bounded to one less than the buffer size, immediately followed by explicit null-termination:

Before (Vulnerable)

strcpy (make,  table[i].make );
strcpy (model, table[i].model);

After (Fixed)

strncpy (make,  table[i].make,  sizeof make  - 1); make[sizeof make   - 1] = '\0';
strncpy (model, table[i].model, sizeof model - 1); model[sizeof model - 1] = '\0';

Why Each Part of the Fix Matters

strncpy(..., sizeof make - 1)
The third argument to strncpy() caps the number of bytes written. Using sizeof make - 1 (i.e., 63) means at most 63 bytes of the source string are copied, leaving room for the null terminator. Critically, sizeof make is evaluated at compile time from the actual buffer declaration — it's not a magic number that could drift out of sync.

make[sizeof make - 1] = '\0'
This is the part many developers forget. strncpy() does not guarantee null-termination when the source string is longer than the limit — it simply stops copying and leaves the remaining destination bytes as-is (or zero-filled, depending on the implementation). Explicitly setting the last byte to '\0' ensures the buffer is always a valid C string, regardless of input length.

sizeof make vs. a hardcoded 64
Using sizeof make instead of 64 is a defensive coding practice: if the buffer size ever changes in a refactor, the bounds check automatically updates. Hardcoded sizes are a maintenance hazard.

This two-part pattern — strncpy + explicit null-termination — is the canonical safe replacement for strcpy in fixed-size buffer scenarios in C.


Prevention & Best Practices

1. Never Use strcpy() with External Input

strcpy() has no place in modern C code that handles user-controlled or file-derived data. Treat it as a code smell during review.

Unsafe Safe Alternative
strcpy(dest, src) strncpy(dest, src, sizeof(dest)-1); dest[sizeof(dest)-1] = '\0';
strcpy(dest, src) strlcpy(dest, src, sizeof(dest)); (BSD/macOS, or via libbsd)
strcpy(dest, src) snprintf(dest, sizeof(dest), "%s", src);

2. Use strlcpy() Where Available

strlcpy(dest, src, size) always null-terminates and returns the length of the source string, making truncation detection easy. It's available on BSD systems and macOS natively, and via libbsd on Linux.

// Even cleaner than strncpy + manual null-term
strlcpy(make,  table[i].make,  sizeof make);
strlcpy(model, table[i].model, sizeof model);

3. Validate at the Source

When reading metadata from files, validate field lengths before copying:

if (strlen(table[i].make) >= sizeof make) {
    // Log warning, skip or truncate intentionally
}

4. Enable Compiler Hardening Flags

Modern compilers can catch many buffer overflow patterns:

gcc -Wall -Wextra -Wformat-security -D_FORTIFY_SOURCE=2 -fstack-protector-strong

_FORTIFY_SOURCE=2 replaces strcpy() with a checked version at compile time when the destination buffer size is statically known.

5. Use Static Analysis

Tools that can detect this class of vulnerability:

  • Semgrep: c.lang.security.insecure-use-strcpy
  • cppcheck: --enable=warning flags unsafe string functions
  • Coverity / CodeQL: Taint analysis from file I/O to string copy sinks
  • clang-analyzer: alpha.security.ArrayBound

6. CWE and OWASP References

  • CWE-120: Buffer Copy without Checking Size of Input ("Classic Buffer Overflow")
  • OWASP: Buffer Overflow — describes stack and heap corruption risks
  • SEI CERT C: STR31-C — Guarantee sufficient storage for strings

Key Takeaways

  • strcpy() into a fixed buffer is always a vulnerability when the source is externally influenced — in nikon_3700(), the source is ultimately derived from RAW file metadata, making this directly exploitable via crafted files.
  • strncpy() alone is not sufficient — you must follow it with dest[sizeof(dest) - 1] = '\0' to guarantee null-termination. The fix in this PR correctly does both.
  • Use sizeof buffer not a hardcoded integerstrncpy(make, src, sizeof make - 1) is safer than strncpy(make, src, 63) because it stays correct if the buffer is ever resized.
  • RAW image parsers are high-value attack surfaces — they process binary data from untrusted files, often in batch workflows, making memory corruption bugs in this layer particularly dangerous.
  • Compiler hardening flags (_FORTIFY_SOURCE=2, -fstack-protector-strong) provide a defense-in-depth layer but are not a substitute for fixing the root cause in source code.

How Orbis AppSec Detected This

  • Source: Camera make/model strings read from a hardcoded lookup table in nikon_3700(), indexed by bits derived from RAW image binary data (dp[8] and dp[20]), making the effective source attacker-controlled via crafted RAW files.
  • Sink: strcpy(make, table[i].make) and strcpy(model, table[i].model) at lightcrafts/coprocesses/dcraw/dcraw_lz.c:1334–1335, writing into fixed 64-byte character arrays with no length check.
  • Missing control: No bounds check on the length of table[i].make or table[i].model before the copy; no use of length-limited string copy functions.
  • CWE: CWE-120 — Buffer Copy without Checking Size of Input.
  • Fix: Both strcpy() calls replaced with strncpy() bounded to sizeof(buffer) - 1, immediately followed by explicit null-termination of the last byte.

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 in nikon_3700() is a textbook example of why strcpy() is considered dangerous in C: it's a one-line mistake that creates a critical, exploitable vulnerability. The fix is equally concise — two lines of strncpy() with explicit null-termination — but the consequences of leaving it unfixed in a RAW image parser could be severe, from memory corruption to full arbitrary code execution via a crafted photo file.

For developers working in C or C++ on file-parsing code: treat every strcpy() as a code review flag. Ask "what is the maximum length of the source, and is it bounded by the destination?" If the answer is "I'm not sure," it's time to switch to a length-limited alternative. The pattern strncpy(dest, src, sizeof(dest) - 1); dest[sizeof(dest) - 1] = '\0'; is your safe default until you can adopt strlcpy() or a higher-level string abstraction.


References

Frequently Asked Questions

What is a buffer overflow in C?

A buffer overflow occurs when a program writes more data into a fixed-size memory buffer than it can hold, overwriting adjacent memory. In C, functions like strcpy() copy bytes until a null terminator is found, with no regard for the destination buffer's declared size.

How do you prevent buffer overflow in C string copying?

Replace strcpy() with strncpy(dest, src, sizeof(dest) - 1) and always explicitly set dest[sizeof(dest) - 1] = '\0' afterward. Alternatively, use strlcpy() where available, or snprintf() for formatted copies.

What CWE is buffer overflow?

Unbounded string copy buffer overflows are classified as CWE-120 ("Buffer Copy without Checking Size of Input"). Related identifiers include CWE-121 (stack-based) and CWE-122 (heap-based) depending on where the buffer is allocated.

Is strncpy() alone enough to prevent buffer overflow?

No. strncpy() prevents writing beyond the specified length, but it does not guarantee null-termination if the source is longer than the limit. You must always follow strncpy() with an explicit null-terminator assignment: dest[sizeof(dest) - 1] = '\0'.

Can static analysis detect strcpy() buffer overflow?

Yes. Tools like Semgrep, Coverity, cppcheck, and clang-analyzer can flag unsafe strcpy() calls. Orbis AppSec's multi-agent AI scanner detected this exact pattern and automatically generated a fix pull request.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #387

Related Articles

medium

How buffer overflow happens in C kernel PTY subsystem (tty_ptmx.c) and how to fix it

A stack buffer overflow vulnerability was discovered in `tty_ptmx.c`, the kernel-level pseudo-terminal multiplexer component, where an unchecked `sprintf()` call at line 293 could overflow the `device_name` buffer by combining `root_path` and `dev_rel_path` without bounds validation. Because this code executes in kernel context during PTY device creation, successful exploitation could lead to kernel memory corruption, privilege escalation, or system crashes. The fix replaces the unbounded `sprin

medium

How buffer overflow happens in C ImageMagick drawing-wand and how to fix it

ImageMagick's drawing-wand component contained a critical buffer overflow vulnerability in the MVGPrintf() function where vsprintf() was used without bounds checking. By switching to snprintf() with proper size constraints, the fix prevents attackers from overflowing the MVG buffer through crafted SVG files and achieving arbitrary code execution.

critical

How buffer overflow in URL parsing happens in C++ HTTP client and how to fix it

A critical buffer overflow vulnerability in the HTTP client's URL parsing function allowed attackers to overflow a stack-allocated host buffer through specially crafted URLs with excessively long hostnames. The vulnerability enabled arbitrary code execution by overwriting the return address. The fix adds proper bounds validation before the memcpy() operation to ensure the hostname length never exceeds the destination buffer size.

critical

How heap buffer overflow happens in C WiFi frame capture and how to fix it

A critical buffer overflow vulnerability in the ESP32 WiFi frame capture feature (feat_capture_hs.c) allowed attackers within WiFi range to craft oversized 802.11 frames that would overflow heap buffers and achieve remote code execution. The fix adds explicit length validation before memcpy operations and rejects oversized frames rather than silently truncating them.

critical

How integer overflow in _wopendir() happens in C Windows dirent and how to fix it

A critical integer overflow vulnerability in `include/compat/dirent_msvc.h` allowed an attacker-controlled directory path length to wrap the `sizeof(wchar_t) * n + 16` allocation calculation, resulting in a dangerously undersized heap buffer. Subsequent writes to that buffer caused a heap overflow, enabling potential memory corruption or code execution on Windows systems. The fix adds a pre-allocation bounds check and proper errno signaling to safely reject overflow-inducing inputs.

critical

How buffer overflow in SCSI command handling happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in libretro-common's CDROM handling code where the `cdrom_send_command_win32()` function copied an arbitrary number of bytes into a fixed 16-byte SCSI Command Descriptor Block (CDB) buffer without validation. This vulnerability could allow an attacker using a malicious CDROM image or USB device to corrupt memory and potentially execute arbitrary code. The fix adds a simple bounds check before the memcpy operation to ensure cmd_len never exc