Five habits prevent most serious C bugs: check every buffer bound, guard integer arithmetic before using it for sizes, never pass user input as a format string, give every allocation one clear owner, and build with strict warnings plus sanitizers in testing. The SEI CERT C standard gives rules for each.
1. Check bounds on every write
Out-of-bounds writes, CWE-787 in MITRE's catalog, happen when code writes past the end or before the start of a buffer. They corrupt memory and can allow code execution. The cause is usually a size that the code trusted without checking.
char name[16];
snprintf(name, sizeof name, "%s", input);
snprintf stops at the buffer size and always terminates. Compare it with strcpy, which copies until the source ends, wherever that is. Prefer functions that take a size, and pass the real size of the destination, not of the source.
2. Guard integer arithmetic
CWE-190, integer overflow or wraparound, occurs when a calculation produces a value too large for its type. It turns dangerous when the result is a size. If count * size wraps to a small number, malloc returns a small block and the later copy overruns it.
if (count > SIZE_MAX / size) return NULL;
void *p = malloc(count * size);
Check before multiplying. Use unsigned types for sizes, and be careful when mixing signed and unsigned values in comparisons.
3. Never use input as a format string
CWE-134 covers use of an externally controlled format string. The wrong call is printf(user_text). The right one fixes the format and passes the data as an argument.
printf("%s", user_text);
An attacker who controls the format can read memory or write to it through format specifiers. Compilers can warn about non-literal formats if you enable the right flags.
4. Give each allocation one owner
Use after free, CWE-416, happens when code uses memory after releasing it. It often comes from two pointers to the same block with no agreement on who frees it. Adopt a rule: one function or structure owns each allocation, and others borrow. Set pointers to NULL after freeing, and write cleanup in one place per function so every error path runs it.
Document ownership in function comments: "returns a newly allocated string, caller frees." Ambiguity here is where bugs hide.
5. Let the toolchain help
Enable strict compiler warnings and treat them as errors in CI. Run tests under AddressSanitizer and UndefinedBehaviorSanitizer, which catch many memory errors at run time. Add static analysis, and fuzz any code that parses untrusted input, such as file formats and network messages. Keep the toolchain current, because newer compilers add checks.
Where do the rules live?
The SEI CERT C Coding Standard from Carnegie Mellon's Software Engineering Institute is a set of rules and recommendations for secure C, organized by topic, including arrays, integers, memory and input and output. Use it as a review checklist and as the basis for static analysis rules. Each item gives noncompliant and compliant examples.
A constructed example: a small network daemon parses a length-prefixed message. The code reads a 32-bit length, allocates that many bytes plus one, and copies the payload. A malicious sender chooses the maximum value, the addition wraps to zero, and a tiny block receives a huge copy. The fix is to validate the length against a protocol maximum, check the addition and cap the allocation. A fuzzer would have found this in minutes.
Safeguard's code scanning can flag some of these patterns in C projects during review and in the pipeline, and its dependency scanning checks the libraries you link. They support, but do not replace, careful review and testing.
How to check or apply this
- Replace unbounded string functions with size-aware versions and pass destination sizes.
- Wrap allocation size calculations in overflow checks.
- Search for
printf,syslogand similar calls with non-literal formats. - Write ownership notes for every function that allocates or frees.
- Build with strict warnings as errors, and run tests under sanitizers.
- Fuzz every parser that reads untrusted data.
Quick check
Is C unsafe by nature?
It gives no automatic bounds or lifetime checks, so the programmer carries that duty. Discipline, standards such as CERT C and tooling make it manageable.
Sources
- SEI CERT C Coding Standard
- MITRE CWE-787, Out-of-bounds Write
- MITRE CWE-190, Integer Overflow or Wraparound
- MITRE CWE-134, Use of Externally-Controlled Format String
- MITRE CWE-416, Use After Free
Last reviewed: 18 October 2026.