skip to content

Address-space randomisation is on: what exactly does one leaked pointer buy an attacker?

level: middleimportance: should knowfreq 55%

answer

  1. the base moves, the offsets do not
  2. one pointer collapses a whole mapping
  3. bases are drawn per mapping
  4. one bug became two bugs
  5. low bits within a page never move

basics

~20 s

Randomisation's whole protection is a secret: where things are. One leaked address recovers it, because everything inside a mapping moves by the same offset, turning a probabilistic guess into a deterministic exploit against that process.

solid answer

~50 s

Randomisation picks a fresh base for each mapping at process start - the stack, the heap, the region shared libraries land in, and the executable itself when it is built position independent. What it does not randomise is the *internal* layout: the distance from a library's base to any function or instruction fragment inside it is fixed when that library is built. So one disclosed pointer subtracts to the base, and the base plus a fixed offset gives every address the attacker wants. The same reasoning is per mapping - a leaked stack pointer says nothing about the library base, because those bases were drawn separately. In practice the mitigation converts a one-bug exploit into a two-bug exploit: the attacker now needs a disclosure primitive as well as the corruption. That is the price, and an uninitialised buffer echoed back in a reply, an out-of-bounds read or an error string quoting a pointer all pay it.

go deeper

for a junior

Know that randomisation hides where code and data sit rather than changing the code itself, and that the base is chosen once when the process starts.

for a middle

Explain why offsets inside a mapping are fixed at build time, so one leaked pointer yields the mapping base and every address in it, while other mappings stay unknown.

for a senior

Demonstrate that you rate a memory-disclosure defect and a corruption defect in the same reachable surface as one exploit, and name the disclosure sources a network daemon actually leaks through.

for a principal

Be able to say what randomisation genuinely buys - the end of the copy-pasted exploit - and refuse to let that be recorded as the class being handled.

## What randomisation randomises At process start the loader places each region at a base chosen at random: the stack, the heap, the region that shared libraries are mapped into, and - if the executable was built position independent - the program image too. The point is that the attacker's chain needs *addresses*: the address of a resident function to return into, the address of an instruction fragment, the address of a controlled buffer. If those move on every start, an exploit written against one machine does not run on the next. ## What it does not randomise The distance from a mapping's base to anything inside it. A library's internal layout is fixed at build time, so if the attacker learns the runtime address of any one thing inside it, subtracting the known build-time offset yields the base, and the base plus any other build-time offset yields any other address inside that mapping. One pointer collapses the whole mapping. Two consequences follow immediately. - The leak has to be *of the right mapping*. A stack address does not give up the library base and vice versa, because those bases were drawn independently. An attacker who needs both usually needs a disclosure that reaches both, or two leaks. - Randomisation is page-granular, so the low bits that index within a page are not randomised at all. That is why partially overwriting only the low bytes of a stored pointer is a real technique: it needs far less unknown information than forging a full address. ## Why this makes it a price rather than a fix Before randomisation, one memory-corruption defect was an exploit. After it, one defect is usually a crash, and an exploit needs a *second* capability: something that discloses memory. The catalogue of disclosure sources in a memory-unsafe network daemon is unglamorous and long: - a reply built from a buffer that was never fully initialised, so the tail carries whatever the last request left there, including pointers; - an out-of-bounds read where a length or index taken from the request is trusted past the data actually written; - a format-string defect that prints stack contents; - an error or diagnostic string that quotes a pointer value back to the caller; - a data structure serialised out with an embedded pointer field that nobody thought was reachable. None of those are exotic, and this is the honest core of the subject: the second bug is a cost, not an impossibility. Add a month of somebody's time to the estimate and the cost is met. ## Guessing instead of leaking Where the entropy in the base is small, the attacker can simply guess. On 32-bit systems the number of possible bases was small enough that brute force over a service that survives failed attempts was practical. Moving to 64-bit addresses raised that number enormously, which is precisely why the leak - not the guess - became the standard route. Note the shape of that history: the mitigation was not broken, it was *repriced*, and the attacker moved to the cheaper of two options. ## Directionality worth stating precisely A leaked address proves where one object is *in that process, at that moment*. It does not prove the same layout holds in another process; a freshly started process gets a fresh draw. This matters when you reason about whether a disclosure is fatal: a leak in a short-lived process spawned per request is worth much less than the same leak in a daemon whose workers all share one draw, because in the second case the attacker can leak in one interaction and corrupt in another. ## An out-of-bounds read is not a lesser bug Engineers routinely triage a read-only out-of-bounds defect as low severity because it does not corrupt anything. Against a hardened target it is the *enabling* half of the pair. The exploit needs both halves, and the market price of the corruption defect goes up or down depending on whether a disclosure is available in the same reachable surface. When you rate two defects in the same daemon, rate them as a set. ## What this means for the argument you will actually have Someone will say the layout is random, therefore the class is handled. The precise reply is that randomisation stores a secret and the exploit's job is now to learn that secret; secrets in a process that talks to untrusted input have many ways out. What randomisation reliably buys is that a copy-pasted public exploit no longer works unmodified - which is genuinely worth having, and is a statement about which adversaries are priced out, not about the defect.

  • Does a leaked stack address help an attacker locate a library function?
    No. Each mapping gets its own base draw, so a stack pointer fixes the stack region and nothing else. The attacker either needs a disclosure that reaches the library mapping too, or a fragment reachable via a pointer already stored on the stack, such as a saved return address into library code.
  • Why do experienced people refuse to triage an out-of-bounds read as low severity?
    Because against randomisation it is the half that supplies the address secret. A corruption defect and a disclosure defect in the same reachable surface are one exploit, not two minor issues, so they should be rated as a set rather than individually.
  • What does randomisation still buy if leaks are this common?
    It ends the copy-pasted exploit. A public proof of concept written against one build no longer runs unmodified, so the population that can use the defect shrinks to those who can find or buy a disclosure and rebuild the chain. That is a real reduction in who can pay, not a claim of safety.

saying these in an interview costs you the question

  • Thinks the layout is re-randomised during the life of a process
  • Believes one leaked pointer reveals all mappings at once
  • Treats an out-of-bounds read as harmless because it writes nothing
  • Assumes 64-bit entropy makes the mitigation absolute
  • Says randomisation hides the code rather than its address

context