Why can a backdoored compiler reinfect itself when rebuilt from clean, audited source?
answer
- the source is not the binary that ran
- the compiler compiles the compiler
- two rules, one of them self-reproducing
- delete the malicious source, keep the binary
- needs a second, independent toolchain to disprove
basics
~20 sBecause the compiler that does the rebuild is a binary, not its source. A subverted compiler can recognise that it is compiling the compiler and re-insert both the backdoor and the rule that re-inserts it, so the clean source produces a backdoored binary again.
solid answer
~50 sThis is Ken Thompson's trusting-trust attack, from his 1984 Turing Award lecture. The subverted compiler binary carries two rules: when it compiles a chosen target program, insert a backdoor; when it compiles *the compiler's own source*, insert both rules into the new compiler binary. Once that binary exists, the malicious source can be deleted. Every subsequent compiler is built by the previous compiler, so the payload propagates through generations of perfectly clean, fully audited source. The general lesson is that **auditing source can never establish that a binary is clean**, because the binary was produced by another binary you also did not audit. The modern form is rarely a hand-built C compiler - it is the shared base build image, the toolchain distribution or the build plugin that every team inherits without ever building it themselves.
go deeper
Recall the one-line shape: the compiler that builds the compiler can insert a backdoor and the rule that re-inserts it, so clean source keeps producing a dirty binary.
Explain the three moves and, in particular, why the self-reproducing rule lets the attacker delete the malicious source. Then name where the inherited binary lives in a modern pipeline.
Demonstrate that you can answer "what would convince you it is clean" without hand-waving - independent bootstrap and comparison, a deliberately small trusted base, and an explicit statement of what remains trusted rather than verified.
Own the decision about how large your organisation's trusted computing base is allowed to be, and be able to defend in writing which toolchains are trusted on the strength of a producer rather than of evidence.
## The attack Ken Thompson described this in his 1984 Turing Award lecture, "Reflections on Trusting Trust". Build it in three moves. **Move one.** Modify the compiler's source so that when it compiles a particular target program - his example was the login program - it silently emits a backdoor. This works, but it is visible: the malicious code sits in the compiler's source for anyone to read. **Move two.** Add a second rule: when the compiler compiles *the compiler's own source*, it emits a binary containing both rules. The compiler now knows how to reproduce itself, payload included. **Move three.** Compile the malicious compiler source once, keep the resulting binary, and delete the malicious source. From now on, the compiler's source is entirely clean and will pass any audit. But the clean source is compiled by the previous compiler binary, which re-inserts both rules. Generation after generation of honest source produces a backdoored binary, and every backdoored binary keeps backdooring the target program. The self-reproducing step is what makes it durable. Without it, the next honest rebuild would wash the payload out; with it, the rebuild is the propagation mechanism. ## Why this is not a historical curiosity Almost nobody bootstraps their own compiler today, which is exactly the point: the trusted binary has moved, not disappeared. Its modern residence is: - the **base build image** every team's pipeline starts `FROM`, which nobody on the team built and few have inspected; - the **toolchain distribution** - a downloaded compiler, SDK or interpreter - trusted because of where it was downloaded from; - **build plugins and code generators** that run with full privilege over the source tree and are updated automatically; - the **runner image** itself, with its preinstalled tools and agents. An illustrative shape with a different asset at stake: an industrial controller's firmware is cross-compiled inside a shared toolchain image, and that image was rebuilt from a poisoned upstream toolchain. The firmware sources are clean, the review is genuine, and the pipeline's final step signs the image with the organisation's release key. The result is a validly signed firmware image containing code nobody wrote, running on equipment where the asset at risk is the physical safety of a process rather than anyone's data. Every artefact-side check passes, because each one asks a question the attack does not answer to. ## What evidence could ever establish a toolchain clean This is the useful interview turn, and the answer has a hard shape. **"We read the repository" is not an answer.** Not the compiler's repository, not the image's build definition, not the plugin's source. Source review tells you what the source says; the attack lives entirely in the gap between a source and the binary that claims to be its compilation. **"It is signed" is not an answer either.** A signature identifies who published the binary and shows it has not changed since. It carries no claim that the binary corresponds to the published source. A subverted vendor build signs the subverted output exactly as happily as a clean one. What can actually produce evidence: - **Bootstrapping from an independent implementation.** David A. Wheeler's *diverse double-compiling* is the formal version: compile the compiler's source using a second, independently produced compiler to get an intermediate, use that intermediate to compile the compiler's source again, and compare the result with the binary under suspicion. If they match, the suspect binary is behaving as its source describes; if they differ, something is injecting. It requires the compilation to be deterministic and requires the second compiler to be independently subverted-or-not - two compilers from the same poisoned lineage prove nothing. - **Shrinking the trusted base.** You cannot verify everything, so reduce what has to be trusted: fewer distinct toolchains, pinned and inventoried, built from a small bootstrap chain rather than pulled fresh, and owned by someone whose job it is. - **Rooting trust outside the artifact.** Whatever assurance you have ultimately comes from something other than the binary itself - who produced it, under what conditions, and what independent parties can corroborate about that production. Notice that this is a claim about *how it came to be*, which is a different kind of claim from *what is inside it* or *who vouches for it*. ## The takeaway to state out loud Trust in a build output is never established by reading code. It is established by constraining and evidencing what produced the output, and it always bottoms out in something you decided to trust without verifying. The engineering question is not "how do I remove that" - you cannot - but "how small, how explicit and how deliberately chosen is it?"
- Nobody on my team builds a compiler. Where does this attack live for us?In whatever binary you inherit and never build: the base build image, the downloaded toolchain or SDK, the language runtime on the runner, the build plugins and code generators that execute over your source. Each is a binary you run at full privilege on the strength of where you got it, which is precisely the trust relationship the attack targets.
- What evidence would actually convince you a toolchain binary is clean?Nothing you can read in its source. The usable answer is diverse double-compiling - rebuild the compiler's source through a second, independently produced compiler and compare the output with the suspect binary - plus deliberately shrinking the set of toolchains you have to trust at all. Absent that, you are trusting a producer, and you should say so explicitly rather than call it verified.
- Does signing the base image solve it?No. A signature tells you who published the image and that it has not been altered since publication. It makes no claim that the image's contents match its published build definition, so a subverted producer signs a subverted image with a perfectly valid key. Signing changes who you are trusting, not whether verification happened.
A photocopier that quietly adds a line to any document - and, when asked to copy its own repair manual, also adds the instruction to keep adding the line. Reprinting the manual from a pristine original does not change the machine.
saying these in an interview costs you the question
- Says recompiling from clean source removes the backdoor
- Claims auditing the compiler's source proves the binary safe
- Treats trusting-trust as theory with no modern analogue
- Believes a signed toolchain image is evidence of clean behaviour
- Suggests comparing two compilers from the same lineage