cybersecurity · intermediate · ~20 min

Bounded string concatenation (drop-in safer strncat)

Defensive concat with truncation reporting; the strlcat contract.

Challenge

Implement size_t safe_strncat(char *dst, size_t cap, const char *src):

  • dst is a NUL-terminated string in a buffer of total size cap.
  • Append as much of src as fits — leaving room for the NUL terminator at the end.
  • Always NUL-terminate dst (even if dst was malformed, do your best).
  • Return the total length of the string the caller would have produced if cap were infinite (the way snprintf and strlcat do — useful for detecting truncation).

If dst doesn't already end in NUL within cap bytes, return cap and leave dst unchanged (mirrors strlcat's defensive behaviour).

Examples

char buf[16] = "hi";
safe_strncat(buf, 16, "!") -> 3, buf == "hi!"

char buf[6] = "hi";
safe_strncat(buf, 6, "!world") -> 8, buf == "hi!wo"     // truncated, return == would-be length

char buf[16] = "";
safe_strncat(buf, 16, "abc") -> 3, buf == "abc"

char buf[16] = "hello";
safe_strncat(buf, 16, "") -> 5, buf == "hello"

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

dst NUL-terminated within cap bytes; src NUL-terminated.

Output format

See API.

Constraints

No undefined behaviour even if dst doesn't fit a NUL in 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.