Arrays & Strings · intermediate · ~12 min

String safety and bounded operations

Adopt the bounded-copy / explicit-length pattern.

Overview

'Safe string handling' in C means knowing the buffer size at every operation and never trusting the input's length. C's traditional strcpy, strcat, sprintf, and gets have no size argument — they keep writing until the source runs out, even past the destination's end. Replace them with bounded equivalents.

Why it matters

The buffer overflow is the #1 historical security vulnerability in C. A single strcpy of an attacker-controlled string into a fixed buffer can let them overwrite the return address and run arbitrary code. Modern compilers add stack canaries to detect this, but the only real fix is to never write the unsafe call in the first place.

Core concepts

Bounded copy. Use snprintf(dst, sizeof dst, ...) instead of sprintf. Use strlcpy(dst, src, sizeof dst) (BSD/macOS) instead of strcpy. Always reserve the NUL. Every bounded function leaves room for the terminator inside the cap. Don't trust strncpy. It pads with NULs but won't terminate if src is exactly n bytes — the worst-of-both-worlds API.

Syntax notes

char dst[64];
snprintf(dst, sizeof dst, "User: %s", username);  // safe — truncates if too long
strlcpy(dst, src, sizeof dst);                       // BSD; macOS, glibc since 2.38
/* avoid: strcpy(dst, src); strcat(dst, more); sprintf(dst, ...); */

Lesson

The unsafe family (strcpy, strcat, sprintf, gets) writes into the destination assuming there's room. The bounded family (strncpy, strncat, snprintf, fgets) takes a size limit — but each has a quirk.

The simplest mental model: never write into a buffer without knowing its size, and never copy without bounds-checking. Modern code passes both pointer and capacity (buf, size_t cap) everywhere.

Code examples

int safe_copy(char *dst, size_t dst_sz, const char *src) {
    if (dst_sz == 0) return -1;
    size_t n = strlen(src);
    if (n >= dst_sz) { dst[0] = 0; return -1; }
    memcpy(dst, src, n + 1);
    return 0;
}

Common mistakes

  • strncpy doesn't always NUL-terminate.
  • snprintf truncates silently — check the return value to know if your data fit.

Debugging tips

Compile with -Wall -Wextra -Werror=format-security to catch the common patterns. ASan (-fsanitize=address) detects overflows at runtime. valgrind can find them too, though slower. Annotate buffer sizes in macros — #define LOG_LINE_SIZE 256 — and use it everywhere consistently.

Memory safety

Every string operation has two sources of size: the source string (strlen(src)) and the destination buffer (sizeof dst). The destination is the one you can trust at compile time; the source is whatever an attacker hands you. Always cap by the destination.

Real-world uses

Every CGI script, every HTTP header parser, every config file reader — anywhere external text crosses into your program. Real exploited CVEs include OpenSSH (CVE-2002-0083), wu-ftpd (CVE-1999-0368), and dozens of router firmwares.

Practice tasks

  1. Replace a strcpy with snprintf and verify truncation works. 2. Audit a small C file for unbounded calls. 3. Write void safe_join(char *dst, size_t cap, const char *a, const char *b) that builds a + ' ' + b with overflow protection.

Summary

Safe string handling means: every write is bounded by the destination size, every result is NUL-terminated, and strcpy/strcat/sprintf/gets are forbidden in production code. Make this a habit and you eliminate an entire class of CVEs.

Practice with these exercises