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
•Technically reviewed by Anupam Mediratta•Published June 6, 2026•Reviewed 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)

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.


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 integer — strncpy(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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #387

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.