omer@sec:~$

$ cat how-a-program-runs-the-stack.md

How a program actually runs: the stack, in plain terms

A lot of security makes more sense once you understand what actually happens when a program runs. I study this in a home lab — not to attack anything, but because you can’t defend a system whose behaviour you can’t picture. So here’s the mental model I wish I’d had earlier: the call stack.

What the stack is

When your program calls a function, the CPU needs to remember a few things: where to return to afterwards, and some room for that function’s local variables. It stores them in a region of memory called the stack, which grows and shrinks like a stack of plates — last in, first out.

int add(int a, int b) {
    int result = a + b;   // 'result', 'a', 'b' live on the stack
    return result;        // then the CPU returns to the caller
}

Each function call pushes a new “frame” onto the stack: its local variables and the return address (where execution continues when the function finishes). When the function returns, its frame is popped off.

Watching it happen

You don’t have to take this on faith — you can watch it. In a lab, gdb lets you pause a program and look at the stack frames:

(gdb) break add
(gdb) run
(gdb) backtrace     # shows the chain of function calls
(gdb) info frame    # shows this frame: locals, return address

Seeing the return address sitting right next to local variables is the moment it clicks. The data a function works with and the pointer that controls where the program goes next live close together.

Why this matters for defence

That closeness is exactly why a whole class of classic bugs exists: if a program writes more data into a local buffer than it reserved, it can run past that buffer and corrupt what sits next to it — including that return address. That’s the one-sentence idea behind a buffer overflow.

I’m not writing a how-to here — the point is the opposite. Once you can picture the stack, defensive measures stop being magic words:

  • bounds checking and safe string functions keep writes inside the buffer,
  • stack canaries put a known value next to the return address so corruption is detected,
  • ASLR and non-executable stacks make memory layout unpredictable and data non-runnable.

Each of those is a direct answer to “what could go wrong near the return address.” They only make sense if you know what the stack looks like.


That’s the whole reason I spend lab time on this. Understanding how software really runs isn’t about breaking it — it’s what lets you reason about why a mitigation exists and whether a system is actually protected.


← back to all posts