cybersecurity · intermediate · ~15 min · safe pentest lab

Bounds-Safe HTTP Header Line Parser

Split attacker-controlled text into fixed buffers using explicit, pre-copy bounds checks and deny-by-default rejection, instead of trusting input length.

Challenge

A gateway that terminates HTTP for a backend must split each request header line — Name: Value — into two fields before it can enforce policy on them (allow-lists, JWT extraction, size caps). The classic mistake is to strcpy the two halves into fixed stack buffers, trusting that an attacker-controlled line will never be longer than the buffer. It will be: an oversized Authorization header or a crafted name is exactly how a remote client smashes a stack frame.

Your job is to write the split defensively. Given a header line, find the first :, copy the name (everything before it) into name and the value (everything after, with one leading space trimmed) into val. Both destinations carry an explicit capacity (ncap, vcap). Deny by default: if there is no colon, or either field plus its NUL terminator would not fit, reject the whole line by returning -1 and touch nothing beyond the buffers you were handed.

Edge cases to handle: a line with no colon at all, an empty value (X-Empty:), a value with two leading spaces (only one is trimmed), a bare : (empty name and value), an exact-fit buffer, and a NULL line.

Example

parse_header_line("Host: cmagic.dev", name, 32, val, 32) -> 0   (name="Host", val="cmagic.dev")
parse_header_line("GET /x HTTP/1.1", name, 32, val, 32)  -> -1  (no colon)

Input format

A NUL-terminated C string line (one HTTP header line, no CRLF), plus two caller-owned output buffers name and val with capacities ncap and vcap in bytes.

Output format

Return int 0 on success (with name and val NUL-terminated), or -1 on rejection. On rejection the output buffers' contents are unspecified but must never be written out of bounds.

Constraints

C11 freestanding-style function; no I/O, no allocation, no globals. ncap and vcap may be any size_t including small values. Trim at most ONE leading space from the value. The split point is the FIRST ':' only. Every attacker-controlled length must be checked against capacity BEFORE any byte is copied.

Starter code

int parse_header_line(const char *line, char *name, size_t ncap, char *val, size_t vcap)
{
    /* TODO: find the first ':' in 'line'. Copy the text before it into 'name'
     * and the text after it (dropping one leading space) into 'val'.
     * Reject with -1 if there is no ':' or if EITHER field plus its NUL
     * terminator would not fit its capacity. Check sizes BEFORE copying. */
    (void)ncap; (void)vcap;
    /* insecure stub: pretends success without bounds-checking anything */
    return 0;
}

Common mistakes

Using strcpy/strcat with no length check; checking length AFTER copying; forgetting to reserve one byte for the NUL (off-by-one); trimming every leading space instead of exactly one; splitting at the last ':' or requiring a space to be present; writing a partial name before discovering the value overflows; dereferencing a NULL line.

Edge cases to handle

No colon in the line (reject). Empty value after the colon. Value with two leading spaces (only one trimmed). Bare ':' giving empty name and empty value. Name that needs exactly ncap bytes (allow) vs one more (reject). Value too long for vcap (reject). NULL line or zero-capacity buffer (reject without dereferencing).

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.