Safeguard
Application Security•

What are the top 5 tips for writing secure C code?

Check every buffer bound, guard integer math, never pass input as a format string, give each allocation one owner, and build with warnings and sanitizers.

Safeguard Security Team
4 min read

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

  1. Replace unbounded string functions with size-aware versions and pass destination sizes.
  2. Wrap allocation size calculations in overflow checks.
  3. Search for printf, syslog and similar calls with non-literal formats.
  4. Write ownership notes for every function that allocates or frees.
  5. Build with strict warnings as errors, and run tests under sanitizers.
  6. 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

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.