Why does a memory-safety defect often stop at a crash rather than reach code execution?
answer
- crash means influence, not control
- you need a primitive you can aim
- non-executable pages force code reuse
- randomised addresses demand a separate leak
- chains, sandbox escapes, reliability
basics
~20 sA crash proves memory was corrupted, not that the corruption can be steered. Reaching execution needs a controlled write or read primitive plus defeat of non-executable data pages, address randomisation, stack cookies and control-flow integrity. Many defects never get there.
solid answer
~50 sCrashing means the attacker influenced memory somewhere; executing code means they control **what** is written, **where**, and **when**, and can then turn that control into a chosen instruction stream. Between the two sit layered mitigations: non-executable data pages force reuse of code already in the process rather than injected instructions, address-space randomisation means the attacker does not know where that code is and needs a separate information leak to find out, stack cookies detect an overwritten return address, control-flow integrity restricts where indirect branches may land, and hardened allocators make heap grooming unreliable. Add sandboxing — execution inside a parser process may still be worthless without a second escape defect — and reliability across builds and allocator states. That is why memory-safety weaknesses are a class with a long, expensive and frequently unsuccessful distance to a working attack.
go deeper
Recall the headline: a crash shows memory was corrupted, not that an attacker can run code. Know the names of the main obstacles — non-executable memory, randomised addresses, stack cookies.
Explain the pipeline from defect to primitive to execution and name what each mitigation forces the attacker to add. Be able to say why an information leak is usually needed alongside the corruption bug.
Show judgment about which defects have a denial-of-service ceiling versus which yield a controllable primitive, and say honestly that 'unexploitable' is a claim with an expiry date rather than a permanent property.
Own the strategic reading: this class buys you time proportional to attacker funding, so the defensible position is to invest in mitigation depth and sandboxing rather than to bet an estate on any single bug staying unconverted.
## What a crash actually establishes A reproducible crash establishes that a memory-safety rule was violated somewhere in the process and that the violation is triggerable by input the attacker controls. It establishes nothing about *control*. The honest reading is: "memory was corrupted; whether the corruption can be steered is unknown." That distinction is the whole reason memory safety sits at the far end of the distance scale. A missing authorization check needs no work between description and attack. A memory-safety defect needs a research project, and a significant share of those projects end with "denial of service only". ## Step one: a primitive, not an accident Exploitation begins by converting the defect into a **primitive** — a repeatable capability described in terms of what the attacker can do to memory: - *Write-what-where*: place a chosen value at a chosen address. - *Relative overflow*: write past the end of a buffer by a controlled amount. - *Arbitrary read*: disclose memory contents from a chosen address. - *Use-after-free*: cause a freed object to be reused while a stale pointer still refers to it. Many defects yield none of these. A null-pointer dereference reads address zero; there is nothing to steer. An unmapped read one page past a mapping kills the process with no attacker-chosen consequence. An integer overflow may produce a wrong length that only ever truncates. These have a **ceiling of denial of service**, and the ceiling is a property of the bug, not of the researcher's patience. ## Step two: the mitigation stack, each one adding distance Even with a good primitive, several independent obstacles stand in the way, and each one costs the attacker separate work: | Mitigation | What it does | What the attacker must add | | --- | --- | --- | | Non-executable data pages | Data regions cannot be executed | Reuse code already in the process instead of injecting any | | Address-space layout randomisation | Module, heap and stack bases move each run | An information leak that discloses a real address | | Stack cookies | A guard value is checked before returning | Avoid the cookie, or leak it, or corrupt something other than the return address | | Control-flow integrity enforcement | Indirect branches must land on valid targets | Find a permitted target that is still useful | | Hardened allocators | Metadata checks, randomised placement | Groom the heap into a predictable shape, repeatedly | The compounding matters more than any single row. Address randomisation alone is often bypassable; address randomisation *plus* the need to reuse existing code *plus* branch-target restriction usually means the attacker needs **two or three separate defects chained**, at least one of them an information disclosure. ## Step three: reliability, and where execution actually lands A one-in-fifty exploit is not a working attack for most purposes. Reliability means surviving different builds, patch levels, allocator states, thread timing and whatever else happens to be in the address space. And landing execution is often not the objective: if the vulnerable parser runs in a restricted sandbox, code execution there buys nothing until a *second* defect escapes it. Modern intrusions through this class are frequently chains, not single bugs. ## The economics that follow from the class All of that work is specialised, uncertain and expensive, so it is performed by people who are funded to do it. The practical consequence is that a memory-safety defect's *usable* audience starts small and only widens if and when someone converts it, whereas a weakness usable on reading has its full audience from the first day it is described. ## The two mistakes to avoid **Overclaiming**: treating a crash as proof of remote code execution. It is not, and severity assigned on that assumption will be wrong. **Underclaiming**: declaring a defect "unexploitable" because a first attempt failed. That verdict has an expiry date — techniques improve, and bugs judged unexploitable have later been converted. The defensible statement is about *expected distance and cost*, not about impossibility. Ranking by class means saying "this one requires a funded research effort with uncertain outcome", not "this one is safe".
- Which memory-safety bugs realistically have a ceiling of denial of service?Null-pointer dereferences, reads just past a mapping into unmapped memory, and length miscalculations that only truncate. In each case the attacker cannot choose the address or the value involved, so there is no primitive to build on. The process dies and that is the whole effect. Contrast a use-after-free on an object with a function pointer, where the attacker may control what replaces it.
- Why does an information-disclosure bug make a separate memory-corruption bug far more valuable?Because address randomisation is what stops most corruption primitives from becoming execution. A disclosure that reveals one real address in a known module defeats it, letting the attacker compute where reusable code lives. That is why chains are the norm: the leak is the enabler, and the pair together is worth far more than either alone.
- Does a hardened sandbox change how you rank a parser memory defect?Yes. If the parser runs with a tight sandbox, code execution inside it is not yet a meaningful outcome; the attacker needs a second defect to escape. That lengthens the distance to a working attack substantially and is a legitimate reason to rank the single defect lower, provided you can actually show the sandbox is in force in your deployment.
saying these in an interview costs you the question
- Treats a reproducible crash as proof of code execution
- Says address randomisation alone makes exploitation impossible
- Declares a defect permanently unexploitable after one failed attempt
- Ignores that real intrusions chain several defects together
- Assumes execution inside a sandboxed parser is already the objective