Introduction

The Wrong Memory

The objective of this book is to develop a particular way of understanding memory-safety bugs in the C programming language. Memory safety (for the purpose of this book) concerns whether accesses remain valid with respect to objects, bounds, storage, and lifetimes.

The C programming language (C) emerged from a design context in which direct manipulation of machine resources was a feature rather than an anomaly. Its relatively low-level memory model permits programmers to manipulate memory addresses directly, without generally enforcing that each resulting access remains within the bounds and lifetime of the object concerned. Many of the difficulties we now call memory-safety problems arise from these design choices.

C provides a low-level model in which memory can be manipulated through an intricate interplay of numbers, addresses, objects, storage, and lifetimes. Programmers must keep the relationships between these elements consistent, because an error in one can propagate through the others. A wrong size can produce an undersized allocation; a pointer calculation can then produce a pointer that appears valid but falls outside that allocation; a subsequent copy can overrun the object. The resulting corruption may remain hidden until much later, when its original cause is difficult to trace.

There is a simple sentence that captures a recurring pattern in many serious C memory bugs:

I calculated, addressed, allocated, copied, or freed the wrong amount or location of memory.

The first central idea of this book is: memory bugs are often chains of incorrect assumptions rather than isolated mistakes.

Memory safety bugs in C are often neatly categorized as:

integer overflow
buffer overflow
out-of-bounds access
use-after-free
double free
memory corruption

These categories are useful for describing failures, but they can hide the relationship between them. Real-world C bugs are often messy, intertwined failures.

Consider a program that receives a count, calculates a size from that count, allocates memory, walks through the resulting object, and eventually copies data into it.

There are many places where something can go wrong.

The count might be wrong.

The calculation might be wrong.

The allocation might be too small.

The pointer might be calculated from the wrong value.

The bounds might be wrong.

The copy might be too large.

The object might no longer exist.

The eventual failure might therefore occur several steps away from the original mistake.

The second central idea of this book is: we should follow the chain rather than examining each operation in isolation.


The memory chain

A useful starting point is:

integer arithmetic
    ->
allocation size
    ->
pointer arithmetic
    ->
bounds
    ->
memory corruption

This is not a rigid sequence. Real programs will skip steps, repeat them, or introduce other operations between them.

'The memory chain' is a way of thinking.

When a number is used to describe memory, we should ask where that number came from.

When an address is calculated, we should ask what object it is intended to identify.

When memory is allocated, we should ask what logical object the allocation is supposed to contain.

When a range is accessed, we should ask why the program believes that range is valid.

And when memory is freed, we should ask whether the pointer still identifies the intended object.

The answers form a chain.


From code to assumptions

C code often looks deceptively local.

For example:


size = count * element_size; // Calculate the total number of bytes needed
char *p = malloc(size); // Allocate a block of memory of that total size on the heap
char *q = p + offset; // Compute a new pointer q by adding an offset
memcpy(q, src, length); // Copy length bytes from src to the memory beginning at q

Each statement is short.

The interesting question is not whether each statement looks reasonable by itself.

The interesting question is whether the assumptions connecting them are true.

What does `count` represent?

What does `size` represent?

What object does `p` point to?

What does `offset` measure?

What is the relationship between `length` and the remaining space?

The answers to those questions turn a collection of statements into a chain of reasoning, forcing you to deduce the global invariants of the system and view the code as a logical proof that memory is allocated and addressed correctly.

A memory-safety bug is simply what happens when the programmer's assumptions in that proof no longer match the actual state of memory.


The wrong amount and the wrong location

The phrase at the heart of this book has two particularly important dimensions:

amount

and:

location.

A program can use the correct location but the wrong amount of memory.

It can use the correct amount but the wrong location.

It can get both wrong.

These cases lead to different manifestations, but the underlying question is the same:

What memory did the programmer believe this operation referred to,
and what memory did it actually refer to?

That question is often more useful than simply asking which bug category applies.


Why follow the chain?

Suppose a program crashes during a copy.

The copy may be where the problem becomes visible, but it may not be where the problem began.

Perhaps an input value was converted incorrectly.

Perhaps an arithmetic calculation overflowed.

Perhaps an allocation was therefore too small.

Perhaps a later pointer calculation still used the original logical size.

Perhaps the copy finally crossed the physical boundary of the allocation.

Looking only at the final copy tells us what went wrong at the end of the chain.

Following the chain tells us why.

That distinction matters both for debugging and for security.

It determines whether we merely patch the operation that happened to fail or repair the assumption that made the failure possible.


A way of reading C

The chapters that follow will use this idea as a recurring method of analysis.

We will not treat integer arithmetic, allocation, pointers, bounds, and memory corruption as unrelated topics.

Instead, we will follow values as they move through a program.

A value may begin as:

input

become:

count

then:

size

then:

allocation

then:

offset

then:

address

then:

memory access.

At each stage, the program makes an assumption about what that value means.

Our job is to determine whether the assumption remains true.

This approach also explains why apparently minor C details can become security vulnerabilities. A small numerical mistake is not necessarily dangerous by itself. It becomes dangerous when the resulting value is trusted to describe memory.


The question to keep asking

Throughout the book, when you encounter code that manipulates memory, return to the same statement:

I calculated, addressed, allocated, copied, or freed the wrong amount or location of memory.

Then ask:

Which one?

Where?

Based on which value?

Calculated how?

Relative to which object?

And what happens next?

Those questions are enough to begin tracing many difficult memory bugs.

The chapters that follow will fill in the technical details.

The goal is not to memorize a longer list of dangerous C constructs.

It is to develop a way of seeing the relationships between numbers, objects, addresses, and memory.

Once those relationships become visible, many apparently different C memory vulnerabilities start to look like variations of the same problem:


the program believed it was operating on one region of memory,
but the calculation, address, allocation, copy, or lifetime
described another.

That is the wrong memory.

Who Is This Book For?

The Wrong Memory is primarily a book about learning how to reason about memory-safety bugs.

It is written for programmers, security engineers, code reviewers, students, and anyone else who wants to understand not only that a C program has a memory-safety problem, but why the problem exists, where it first appears, and how an apparently ordinary operation eventually reaches the wrong memory.

C and systems programmers

If you write C, this book is for you.

You may already know the familiar vocabulary: integer overflow, buffer overflow, out-of-bounds read, out-of-bounds write, use-after-free, double-free, invalid free, and so on. The book does not ask you to forget those categories. Instead, it asks you to look underneath them.

A memory operation depends on a collection of assumptions. A number must represent the right quantity. An allocation must be large enough. A pointer must identify the intended object. A length must describe the range actually available. An object must still be alive. And the code performing the operation must have the right to use or release that object.

When one of those assumptions becomes false, the eventual symptom may be called a particular kind of vulnerability. But the underlying mistake may have happened much earlier.

The book gives you a way to follow that chain.

Security engineers, vulnerability researchers, and code auditors

The book is also intended for people who need to investigate unfamiliar C code.

A useful security review does not stop at finding an apparently dangerous expression. It asks questions such as:

What value controls this memory access?

Where did that value come from?

What was it supposed to mean?

What invariant is supposed to hold?

Where is that invariant first broken?

The early chapters develop this as a practical code-review and vulnerability-analysis technique. Later chapters extend the same reasoning to copying, reading, writing, addressing, lifetime, and ownership. The goal is to make it possible to trace a vulnerability through the program rather than treating each memory-safety bug as an isolated category.

Students and people learning systems security

The book can also be read as a way of developing a mental model for low-level programming.

You do not need to be an expert vulnerability researcher. Some familiarity with C, pointers, arrays, structures, dynamic allocation, and basic integer types will help, but the book builds its central framework progressively.

The exercises are particularly useful if you are learning how to analyse code rather than simply write it. Instead of asking only whether a particular line is "safe" or "unsafe," they encourage you to identify the quantity controlling the access, reconstruct the intended invariant, and find the first place where that invariant can fail.

Experienced programmers who want to understand memory safety more deeply

The book is not only for people who are new to C.

Experienced C programmers may recognise every individual operation discussed here and still find that the connections between them are worth examining. In real programs, the dangerous assumption is often separated from the eventual memory access by several transformations, functions, or layers of abstraction.

The central idea of the book is therefore deliberately broader than "check your bounds."

A value can change meaning as it travels through a program. A count can become a byte size. A size can determine an allocation. An allocation can determine a pointer range. A range can determine a copy. A pointer can outlive the object it once identified. A value that was correct at one stage can therefore become the wrong value at another.

The book is about learning to see those relationships.

Readers interested in memory-safe languages

The book also has a second life beyond C.

The four appendices step back from individual vulnerabilities and ask a larger question: what would it mean for memory safety to be a property of the programming language rather than a collection of responsibilities placed on the programmer?

The first three appendices explore memory safety, semantic invariants, and the possibilities and limitations of changing C itself. They are particularly suited to readers interested in programming-language design, language evolution, and the relationship between a language's semantics and the errors its programmers can express.

These appendices can be read independently of the practical chapters, but they are especially useful after seeing the concrete problems described in the main book. The practical bugs provide the motivation for the more abstract questions.

C programmers considering Rust

The fourth appendix is aimed more specifically at readers making - or considering - the transition from C to Rust.

Rust is often introduced in terms of technical mechanisms: ownership, borrowing, lifetimes, and the type system. But the deeper transition is one of mental models.

If you have spent years programming in C, you may be accustomed to keeping many facts about memory in your head: which allocation a pointer belongs to, how much capacity remains, whether an object is still alive, who owns it, and which aliases may be used at a particular point in the program.

Rust asks you to represent more of those facts explicitly in the program itself.

Understanding the memory-safety problems described in this book can therefore make the motivation behind Rust's constraints much clearer. The question is not simply "How does Rust do things differently from C?" It is "Which assumptions was I previously responsible for maintaining myself?"

You do not need to fit neatly into any of these categories

Ultimately, this book is for anyone interested in the boundary between what a program appears to say and what its memory operations are actually entitled to do.

You may be writing C code, reviewing it, auditing it for vulnerabilities, learning systems programming, studying programming languages, or considering a move to a memory-safe language.

The common thread is curiosity about the underlying question:

What did the program believe about memory, and where did that belief stop being true?

If that question interests you, this book is for you.


← Previous 1 of 12 Next →