Skip to content

CWE-787: Out-of-bounds Write

Overview

Out-of-bounds write happens when a program writes data past the end, or before the beginning, of the buffer it was allocated for - corrupting adjacent memory instead of failing safely. It typically results from a missing bounds check, an incorrect size calculation, or an unsafe copy/format function that doesn't know the destination's real capacity. This is almost exclusively a manual-memory-management problem: languages with automatic bounds checking on normal indexing (Java, C#, Python, safe Rust) don't allow it to happen through ordinary array or buffer access.

Relationship to Other CWEs

  • CWE-787 (this page) - a write that lands outside the bounds of the intended buffer
  • CWE-125 (Out-of-bounds Read) - the read-side counterpart: the same missing-bounds-check root cause produces a read past the buffer instead of a write. A write is generally more severe at the same location because it can corrupt control data - return addresses, function pointers, vtables, heap metadata - and lead directly to code execution rather than only information disclosure. The attacker usually controls both where the bytes land and what they are, so adjacent memory is replaced with a chosen value rather than merely disclosed
  • CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer) - the Class MITRE files both Base weaknesses under, and marks Discouraged for mapping. Where the direction of the access is known, CWE-787 rather than CWE-119 is the level to report at
  • CWE-121 (Stack-based Buffer Overflow) - the one child of this page with dedicated guidance: the same missing check with the corrupted memory on the stack, where it can reach a saved return address. If the finding names a stack buffer, CWE-121 is the closer fit

CWE-787 has more specific children of its own, which is why MITRE marks it Allowed-with-Review. The rest have no page and are covered here: CWE-120, the classic unbounded copy into a fixed-size destination, which is the strcpy shape the C page opens with; CWE-122, the same overflow on the heap rather than the stack; CWE-123, write-what-where; and CWE-124, a write before the start of the buffer.

MITRE also records the weaknesses an out-of-bounds write can follow, each a different way the address goes wrong before any byte is written through it:

Where a finding names one of these alongside CWE-787, it is naming the cause and CWE-787 is the consequence: correcting how the pointer is formed is what closes both, and a bounds check on the write is a backstop rather than the fix.

Risk

Critical: Writing beyond a buffer's boundaries corrupts adjacent memory - crashing the process, altering program state, or overwriting control data in a way that leads to arbitrary code execution.

Remediation Steps

Core Principle: Never write past the bounds of the buffer that was actually allocated; prefer memory-safe abstractions that make out-of-bounds writes structurally impossible over manual bounds checking.

Trace the Data Path

  • Source: Any length, offset, or index value influenced by user input, file data, network data, or an attacker-controlled calculation
  • Sink: The raw memory write - an array index assignment, pointer arithmetic, or a copy/format function writing into a fixed-size destination
  • Missing Controls: No check that offset + length stays within the destination buffer's actual allocated capacity, or a size calculation that can silently overflow before the write happens

Use Bounds-Checked Abstractions (Primary Defense)

  • Prefer containers and types that track their own capacity and refuse an out-of-bounds write over raw arrays and pointers sized and indexed by hand
  • Where a language or library offers both a checked and an unchecked way to write into a buffer, default to the checked one, and drop to the unchecked form only behind an explicit bounds check immediately before the write

Validate Every Write Size Against the Destination's Capacity (Defense in Depth)

  • Check index >= 0 && index < buffer_size before every write. For a range write, confirm offset <= buffer_size and then reject when length > buffer_size - offset - never offset + length <= buffer_size, which performs the addition before the comparison, so a large enough offset wraps the sum back into range and the check passes
  • Never trust a length or offset field taken directly from user input, a file, or the network - validate it against the real destination size, not just against the sender's claimed size

Replace Unsafe Copy/Format Functions

Prefer copy and formatting functions that take an explicit destination capacity over ones that don't, and check the result to confirm the write wasn't truncated or rejected rather than assuming it succeeded.

Harden the Runtime as Defense in Depth

  • Enable compiler protections (stack canaries, ASLR, DEP/NX) so an out-of-bounds write that does happen is harder to turn into code execution
  • Run sanitizers and fuzzers during development to catch out-of-bounds writes before release

Test with Malicious Inputs

  • Oversized length and offset values, including values crafted to overflow a size calculation
  • Negative or very large index values where the code assumes a small positive number
  • Exact boundary values (size - 1, size, size + 1)
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
buffer = allocate(fixed_size)
copy_into(buffer, source, source_length)   // no check that source_length <= fixed_size
// Attack: source_length is taken from an untrusted length field and exceeds fixed_size
// Result: the copy writes past the end of buffer, corrupting adjacent memory

Why this is vulnerable: source_length describes the source and fixed_size describes the destination, and the copy is governed by the one the attacker supplies. That inversion is the weakness. A length arriving in a packet header, a file field, or a protocol frame is a claim about how much data was sent; it is never a statement about how much room there is to put it, and the two are only equal when nobody is trying.

Secure Patterns

// SECURE - pseudo-code
buffer = allocate(fixed_size)
if source_length > fixed_size:
    reject("input too large for destination")
copy_into(buffer, source, source_length)

Why this works: The size is checked against the destination's real capacity before the copy runs, so an oversized or attacker-crafted length is refused rather than reaching the write. A bounds-checked container or copy function that enforces the same rule is better still, because a call site cannot forget it.

Language-Specific Guidance

  • C - safe string/copy functions, explicit bounds checks, AddressSanitizer and compiler hardening flags
  • C++ - std::vector/std::string/std::span, checked .at() access, avoiding raw arrays and manual pointer arithmetic

Additional Resources