OneBug All write-ups

C ·

malloc(strlen()) off-by-one: the heap overflow strcpy hides

malloc(strlen(label)) allocates one byte short because strlen excludes the NUL terminator, so strcpy writes past the buffer. The fix and why it stays hidden.

Test yourself before reading - the write-up below gives it away.

Spot the bug C
1
// Return a heap copy of the given label.
2
char *copy_label(const char *label) {
3
    char *dst = malloc(strlen(label));
4
    if (dst == NULL) return NULL;
5
    strcpy(dst, label);
6
    return dst;
7
}
Powered by OneBug

A small utility function: take a label, return a heap-allocated copy of it. The kind of helper that gets written once and called from a dozen places without a second look, and copies the string correctly almost every time.

// Return a heap copy of the given label.
char *copy_label(const char *label) {
    char *dst = malloc(strlen(label));
    if (dst == NULL) return NULL;
    strcpy(dst, label);
    return dst;
}

Why does malloc(strlen(label)) overflow when strcpy copies the string?

malloc(strlen(label)) reserves room for the visible characters but not the terminating NUL byte that strcpy also writes, so that terminator lands one byte past the end of the block - a one-byte heap overflow. The NULL check on the next line is a correct decoy. Size the buffer strlen(label) + 1.

if (dst == NULL) return NULL; is the decoy. It looks like careful, defensive code - checking the allocation before using it is exactly what you are supposed to do - and it is correct as written. The bug is the size passed to malloc one line above.

The bug: strlen does not count the NUL terminator

strlen(label) returns the number of characters before the terminating NUL byte, by definition - that is the whole point of the function, it measures the string's printable length. malloc(strlen(label)) therefore allocates exactly enough space for the visible characters and no room at all for the terminator that strcpy also writes.

strcpy copies every byte of the source, including the NUL, and stops only after writing it. With a buffer sized strlen(label), that terminator lands one byte past the end of the allocation. This is a real heap write outside the bounds of dst, not a logical error that merely produces a wrong value - it corrupts whatever memory happens to sit right after the allocated block.

It stays hidden because heap allocators commonly round requests up and pad blocks for alignment or bookkeeping. A request for, say, 11 bytes might actually reserve 16 or 24 internally, so the stray terminator lands in unused padding instead of another object's data. The function runs, the string looks right when printed, and nothing crashes - until an allocation happens to be sized exactly to the boundary, or a future allocator, build, or sanitizer configuration removes the slack that was silently absorbing the overwrite.

Why it bites in real code

This is the canonical off-by-one heap overflow, and it is a real memory safety bug class, not a stylistic nitpick. A one-byte out-of-bounds write can corrupt allocator metadata, overwrite an adjacent object's data, or - in the worse cases - clobber a byte that changes control flow, depending on what the heap layout happens to put next to the block. Because it depends on allocator internals and memory layout, it is exactly the kind of bug that passes in development, passes in a debug build, and then surfaces as a baffling intermittent crash or data corruption report from production, often on a different platform or a different allocator than what the code was written against. Tools like AddressSanitizer exist specifically because manual review misses this class reliably - the source line looks completely ordinary.

The fix

Allocate room for the terminator:

char *dst = malloc(strlen(label) + 1);

That one + 1 is the entire fix; nothing else in the function needs to change. Where available, strdup(label) does the size calculation and the copy in one call and is the more idiomatic choice for exactly this pattern. If the destination has a fixed maximum size instead of being freshly allocated to fit, prefer a bounded copy such as strlcpy (where available) or an explicit strncpy plus manual NUL-termination, so a long input cannot overflow a fixed-size buffer either.

War stories

Off-by-one buffer errors are a recognized weakness class, catalogued as CWE-193 (Off-by-one Error), and heap off-by-ones specifically have a long track record as real, exploitable vulnerabilities rather than theoretical concerns. The best-known public example is OpenSSH's channel handling bug, CVE-2002-0083, where an off-by-one in a bounds check led to a heap overflow that was serious enough to be treated as a potential path to full remote compromise. The details of that particular bug differ from this snippet, but it is the standard reference point for why "one byte past the end of a heap allocation" is treated as a security bug, not just a correctness one.

Related bugs

The same "the check looks right, the bound underneath it is wrong" shape appears in C signed/unsigned bounds check bypass, where a comparison that reads as a safe guard is defeated by how C handles mixed-sign arithmetic. Browse the rest of the bug write-ups.