Compare dangerous functions with their safe replacements. Buffer overflows have been a top security vulnerability for decades.
#include <string.h> int main(void) { char buf[8]; // only 8 bytes strcpy(buf, "This is too long!"); // writes 18 bytes β overflow return 0; }
sizeof or a constant).char dst[8]; strcpy(dst, src); // no size check β overflow if src >= 8 bytes
char dst[8]; strncpy(dst, src, sizeof(dst) - 1); dst[sizeof(dst) - 1] = '\0'; // always terminate explicitly
'\0' terminator. You must add it yourself.char dst[8]; int n = snprintf(dst, sizeof(dst), "%s", src); // snprintf always NUL-terminates (when size > 0) // return n is "chars it wanted to write"; truncation if n >= sizeof(dst) if (n >= (int)sizeof(dst)) { fprintf(stderr, "warning: truncated\n"); }
char buf[32]; sprintf(buf, "Hello, %s!", name); // long name overflows buf
char buf[32]; snprintf(buf, sizeof(buf), "Hello, %s!", name);
int n = snprintf(buf, sizeof(buf), fmt, ...); if (n < 0) { /* encoding error */ } else if (n >= sizeof(buf)) { /* truncation */ } else { /* OK: n bytes written */ }
gets has no size parameter and was removed in C11. Always use fgets instead.char line[64]; gets(line); // NG: no size limit
char line[64]; if (fgets(line, sizeof(line), stdin) == NULL) { // EOF or error }
char line[64]; if (fgets(line, sizeof(line), stdin)) { size_t len = strlen(line); if (len > 0 && line[len - 1] == '\n') { line[len - 1] = '\0'; } }
line[len-1] won't be '\n'. Decide whether to loop to read the rest or report an error.atoi can't report errors, and its behavior on overflow is undefined. strtol lets you properly detect "conversion failed" and "out of range."int n = atoi(s); // s="abc" also returns 0 β you can't tell it apart from an error // s="99999999999" produces undefined behavior
#include <stdlib.h> #include <errno.h> errno = 0; char *end; long v = strtol(s, &end, 10); if (end == s) { /* no digits consumed */ } else if (*end != '\0') { /* trailing junk */ } else if (errno == ERANGE) { /* out of range */ } else { /* success: value is v */ }
#include <errno.h> #include <limits.h> #include <stdlib.h> int parse_int(const char *s, int *out) { errno = 0; char *end; long v = strtol(s, &end, 10); if (end == s || *end != '\0') return -1; if (errno == ERANGE || v < INT_MIN || v > INT_MAX) return -1; *out = (int)v; return 0; }
strtod for doubles and strtoul for unsigned long follow the same pattern.| Avoid | Use instead | Notes |
|---|---|---|
strcpy | snprintf / strncpy+NUL | No size check |
strcat | strncat / snprintf | Same issue as strcpy |
sprintf | snprintf | Detects truncation via return value |
gets | fgets | gets was removed in C11 |
atoi / atof | strtol / strtod | Supports proper error detection |
scanf("%s", ...) | fgets + sscanf, or a width like %31s | Unbounded %s is unsafe |
gcc -Wall -Wextra -Wformat-security -Wstack-protector \
-fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 prog.c
-D_FORTIFY_SOURCE=2 adds runtime checks via glibc, and -fstack-protector-strong aborts the program on stack corruption.cppcheck, clang-tidy, or clang --analyze to catch buffer bugs before they ship.-fstack-protector-strong and observe the stack smashing detected message.parse_int: write a strict version that rejects "123abc" and a lenient version that accepts "3.14" and keeps the integer part. Add 10 test cases.char *read_line(FILE *fp, char *buf, size_t size);. If the input exceeds size, discard the rest so the next read starts clean. Return buf, or NULL on EOF.Check your understanding of this lesson.
strcpy?strcpy doesn't check the buffer size, so it's a classic cause of buffer overflows. Use length-bounded variants or snprintf.
Writing past the allocated region is undefined behavior and can cause segfaults or open the door to arbitrary-code-execution vulnerabilities.
sprintf takes no size argument and is dangerous. snprintf accepts a buffer size, making it safe to use.