cybersecurity · intermediate · ~20 min

Bounded string concatenation (drop-in safer strncat)

Defensive concat with truncation reporting; the strlcat contract.

Challenge

Write a sane, truncation-aware string concatenation — the safe replacement for strncat, whose third argument is famously NOT the buffer size.

Task

Implement size_t safe_strncat(char *dst, size_t cap, const char *src) following the strlcat contract:

  • dst is a NUL-terminated string living in a buffer of total size cap.
  • Append as much of src as fits, always leaving room for the terminating NUL.
  • Always NUL-terminate dst.
  • Return the length the result would have had if cap were unlimited (strlen(dst) + strlen(src)), so the caller can detect truncation.
  • If dst has no NUL within cap bytes (malformed), return cap and leave dst unchanged.

Input

  • dst: a buffer holding a NUL-terminated string, total size cap.
  • cap: the full size of the dst buffer in bytes.
  • src: a NUL-terminated string to append.

Output

Returns the would-be total length as a size_t (a value >= cap means truncation happened).

Example

buf="hi"  (cap 16) + "!"       ->   3, buf = "hi!"
buf="hi"  (cap 6)  + "!world"  ->   8, buf = "hi!wo"   (truncated; return is would-be length)
buf=""    (cap 16) + "abc"     ->   3, buf = "abc"
buf="hello"(cap 16)+ ""        ->   5, buf = "hello"
dst with no NUL in cap         ->   cap, dst unchanged

Edge cases

  • Empty src or empty dst.
  • src larger than the remaining space: truncate, but still report the would-be length.
  • dst missing its NUL within cap: return cap, no write.

Rules

  • cap is the buffer size, not a byte budget — do not confuse it with strncat's argument.
  • No undefined behaviour, even on malformed dst.

Why this matters

strncat has a famously hostile API (the third argument is the max bytes appended, NOT the buffer size). Writing a saner version forces you to internalise the off-by-one nightmare that has produced so many CVEs.

Input format

A NUL-terminated dst in a cap-byte buffer, the buffer size cap, and a NUL-terminated src.

Output format

The would-be total length (>= cap signals truncation); dst always NUL-terminated.

Constraints

cap is the buffer size, not a byte budget; no UB even if dst lacks a NUL within cap.

Starter code

#include <stddef.h>
size_t safe_strncat(char *dst, size_t cap, const char *src) { /* TODO */ return 0; }

Common mistakes

Confusing cap (buffer size) with 'max bytes to append' (the strncat trap). Forgetting the +1 for the NUL.

Edge cases to handle

Empty src; empty dst; src bigger than remaining space; dst missing its NUL.

Complexity

O(strlen(dst) + strlen(src)).

Background lessons

Up next

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