Skip to content

Expert · Bits, undefined behaviour and safety · topic 44 of 60 · 25 min

Secure and defensive C

Explanation

Defensive C assumes inputs can be wrong or hostile and that every call can fail. Validate input at the boundary, check every return value (malloc, fopen, scanf, read), use size_t for sizes and check multiplications before allocating, avoid unbounded functions, compile with warnings and hardening flags, and follow an industry rule set such as SEI CERT C or, in automotive and other safety-critical work, MISRA C.

Why C needs extra care

C gives no automatic bounds checks, no automatic memory management and no exceptions. A missed check rarely produces an error message; it produces corrupted memory, a crash much later, or an exploitable security hole. A large share of serious security vulnerabilities in operating systems and browsers over the years have been memory-safety bugs in C and C++ code. Defensive habits are therefore part of writing C professionally.

Check every return value

c
FILE *f = fopen(path, "r");
if (!f) { perror(path); return -1; }

char *buf = malloc(n);
if (!buf) { fclose(f); return -1; }             /* clean up what you already have */

if (fscanf(f, "%d", &count) != 1) { … }
if (fclose(f) != 0) { perror("fclose"); }

Validate input at the boundary

Data from users, files, networks and sensors can be any length and any value. Check it where it enters the program: lengths before copying, ranges before using as indexes or sizes, formats before parsing. Inside the program, functions can then rely on valid data (and use assert to document that assumption, module 11).

Integer overflow in size calculations

c
/* Attacker sends count = 1073741825 records of 4 bytes each.
   count * 4 overflows a 32-bit size, so malloc gets a tiny number,
   and the loop then writes far past the small buffer. */
uint32_t count = read_count();
if (count > SIZE_MAX / sizeof(uint32_t)) return -1;      /* would overflow: reject */
uint32_t *items = malloc((size_t) count * sizeof *items);  /* or use calloc(count, sizeof *items) */

Hardening flags

bash
gcc -std=c17 -O2 -Wall -Wextra -Wformat=2 -Wconversion \
    -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fPIE -pie \
    prog.c -o prog
Typical hardening for release builds on Linux; check your distribution's defaults, many already enable some of these.

Coding standards used in industry

  • SEI CERT C Coding Standard (Carnegie Mellon University): rules and recommendations for secure C, such as "do not form or use out-of-bounds pointers" and "guarantee that storage for strings has room for the null terminator".
  • MISRA C: guidelines for safety-critical embedded software, widely required in automotive (and used in aerospace, rail and medical devices). It restricts risky features: for example dynamic memory allocation after start-up, unrestricted goto, and implicit conversions.
  • Static analysis tools check code against these rules automatically: gcc -fanalyzer, clang-tidy, cppcheck, and commercial tools used by automotive suppliers.
Careful: Never trust a length field inside received data without checking it against the actual amount of data you have. The 2014 "Heartbleed" bug in OpenSSL, which leaked memory from huge numbers of servers, came from trusting such a length and copying that many bytes from a much smaller buffer.
Think of it like this: Defensive C is like the safety checklist pilots follow before every flight, even after thousands of flights: check fuel, check flaps, check instruments. Each item seems obvious, but the checklist exists because skipping "obvious" checks is how accidents happen.
Tip: Adopt three habits today: compile with -Wall -Wextra -Werror, run tests under -fsanitize=address,undefined, and never ignore a return value. They prevent most beginner-to-intermediate C bugs.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Secure and defensive C · 1/6

Secure C

In this lesson you will learn the habits of secure, defensive C.

Prefer a video by a teacher? Find free videos on “secure c programming cert c misra c defensive coding” on YouTube ↗

In real life: Reading a length-prefixed message safely

A server for IoT devices receives messages with a 2-byte length followed by data. Code that trusted the length would read beyond the received bytes. The safe version checks the claimed length against the bytes actually received and against its own maximum before copying.

c
#define MAX_MSG 512
int handle_message(const uint8_t *data, size_t received, uint8_t out[MAX_MSG], size_t *out_len) {
    if (received < 2) return -1;
    size_t claimed = (size_t) data[0] << 8 | data[1];
    if (claimed > received - 2 || claimed > MAX_MSG) return -1;   /* never trust the length field */
    memcpy(out, data + 2, claimed);
    *out_len = claimed;
    return 0;
}

Practice (0/3 done)

Task 1 · Question

Why check count > SIZE_MAX / size before malloc(count * size)?

Task 2 · Question

Which coding standard is widely used for safety-critical automotive C?

Task 3 · Practice

Take a small C program you wrote earlier in this course and review it like a security auditor: list every place that reads external input, every return value that is ignored, every fixed-size buffer and every multiplication used for a size. Fix each finding, add hardening flags to your build, and run it with sanitizers.

Quick quiz

Question 1

1.What caused the Heartbleed bug?

Question 2

2.What is the first habit for robust C?

Question 3

3.What does -fstack-protector-strong add?

Question 4

4.Which tool can statically analyse C code for bugs?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What caused the Heartbleed bug?

Answer: Trusting a length field and copying more bytes than were received

Unchecked length → memory leak to the attacker.

What is the first habit for robust C?

Answer: Checking every return value

Failures must be noticed.

What does -fstack-protector-strong add?

Answer: A canary that detects stack buffer overflows before returning

Aborts on "stack smashing".

Which tool can statically analyse C code for bugs?

Answer: clang-tidy or cppcheck

Also gcc -fanalyzer.

Where must input validation always happen?Related

Answer: On the server

Attackers bypass the browser; the server must check.

From Secure coding basics

What does scanf return?Related

Answer: The number of items successfully read (or EOF)

Check it against the expected count.

From Input and output: printf and scanf

What does malloc return when it cannot allocate?Related

Answer: NULL

Always check.

From malloc, calloc and free

Which flag combination helps catch out-of-bounds and undefined behaviour while practising with GCC?Related

Answer: -fsanitize=address,undefined -D_GLIBCXX_DEBUG

Sanitizers and the debug STL report errors at the faulty line.

From Classic C++ pitfalls in contests and coding rounds

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Bits, undefined behaviour and safety” before its exam: Function pointers and callbacks.
  2. Module 9 exam: Bits, undefined behaviour and safety · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Data structures in C. 60 topics left in the course before the final exam.

← Previous: Undefined behaviour