# korp-installer — threat model, measured on oden 2026-08-16
13 470 lines across 33 modules in `edda/crates/korp-installer/src`. Read against one
question: **what can an attacker who reaches this machine actually do?**
Everything below is either read out of the source or observed in a KVM drive on this
box. Where a claim is inference rather than measurement, it says so.
---
## STATUS BOARD — updated 2026-08-16, later the same day
Rickard read this page and picked rows. This is where each one stands. A ticked line
means it was **seen red first, then green**; anything still moving says so by name.
```
✔ CLOSED TODAY
ATTACK 3 the identity key the installer mints the WireGuard keypair, seals the
is sealed PRIVATE half to the TPM under sha256:0,7, stages 288 B
wg/identity.sea on P3, prints the PUBLIC key on the
console for the hub's [Peer] stanza. korp-start
unseals. Pure Rust TPM 2.0 client, no tss2/FFI/shell.
Real chip: 16/16 PCRs agree with sysfs, seal 192 ms,
unseal 665 ms. Reds seen: TPM_RC_POLICY_FAIL on a moved
PCR (and the SAME blob opens again after PCR_Reset, so
the refusal was the boot state and not a dead blob),
TPM_RC_INTEGRITY on a foreign chip or an edited blob,
and a rotted file caught by its own blake3 before the
chip is ever asked.
ATTACK 1 signed cmdline The one hole that was exploitable today. Both halves
(both halves) shut. (1) The policy default is CLOSED: a command line
with no korp.require_signed= token REQUIRES the anchor,
so deleting a token — or the whole file — refuses by
name instead of downgrading. (2) The authoritative
tokens (korp.require_*, korp.entry=, korp.args=,
korp.env.*, korp.seal_pcrs=, korp.pin=, KORP_*) are
BAKED INTO korp-boot before it is Authenticode-signed,
and \korp\cmdline is merged UNDER them — it may add a
key the baked line does not name (korp.znippy=,
console=, initrd=: the plumbing an installed disk
really owns) and may never supply a policy key, a
KORP_ key or the provenance stamp. Reds seen: the
shipped korp-start printing FAILED policy: on a
stripped line; a hostile ESP's korp.require_signed=0 /
KORP_CMDLINE= / korp.env.* absent from the LoadOptions
the chainloaded PE echoed; and one byte of the baked
korp.require_signed= flipped inside the SIGNED image →
"Access Denied -- rejected probably by Secure Boot"
under enforcing OVMF. edda 326617c · cbedd19 · ce3cd10.
NOT covered: a machine whose korp-boot bakes nothing
(an old or hand-laid ESP) — such a boot refuses any
waiver by name and says "Recompose this medium".
ATTACK 5 PID-1 hex panic decode_hex32 checked a BYTE length and then sliced a
(found + fixed) str; "a" + "é"×31 + "a" is 64 bytes and panicked on the
first pair — as PID 1, i.e. a kernel panic instead of
the named refusal. Input is app.blake3 on the plaintext
ESP. edda 1b95cd4, red-proven.
▶ IN FLIGHT — picked by Rickard, one agent per lane, disjoint files
ATTACK 1 signed cmdline CLOSED, both halves — edda 326617c · cbedd19 · ce3cd10 ·
(closed; verified 7804d1d. require_signed is three-state and ABSENCE now
end to end) means REQUIRED; the authoritative tokens are compiled
into korp-boot BEFORE Authenticode signs it; \korp\cmdline
is merged UNDERNEATH and may never loosen policy; KORP_*
off the ESP is refused (KORP_CMDLINE was a total bypass);
and a waiver needs the korp.cmdline=baked stamp only the
loader writes. Reds on applied output: a blanked token in
the real FAT bytes → FAILED policy; a hostile ESP against
a baked loader → the chainloaded PE echoes the baked
values and none of the ESP's; one byte flipped inside the
signed image → Access Denied under enforcing OVMF.
VERIFIED BY THE MAIN SESSION 2026-08-16: gunnar drive
215.63 s, 2 kernel boots, reboot handoff latched, and the
serial shows `cmdline BAKED (signed, authoritative)`,
`cmdline MERGED`, and the policy line naming its reason.
ATTACK 4 rollback CLOSED against replay — edda cc370aa · 421cb7b ·
(closed against 94ca4bb. The signed message is a 62-byte record (domain
replay; live tag ‖ u64 version ‖ blake3 root), a 4th `rollback-floor`
forgery is not) leg refuses a lower version by name, and the floor record
is WITNESSED by a TPM NV monotonic counter — so restoring
an old floor beside an old signed image is caught, which
is the attack this page describes. Proven on oden's chip.
An attacker present NOW can still read the counter and
author a fresh record: that needs TPM2_NV_Write against a
policy-write index (tpm.rs has Define/Increment/Read/
ReadPublic, no Write) or a PCR-sealed MAC. A machine with
NO TPM keeps the bare ESP floor and SAYS so every boot.
See ATTACK 4 below.
ATTACK 2 attestation CLOSED — edda af96c46 · e26790b · 21fe4df · c99567e ·
(closed; the AK's 421cb7b. TPM2_Quote with an ECC-P256 restricted signing
own provenance AK, and a verifier that refuses a stale nonce, a foreign
is the named limit) key, a structure not composed by a TPM, a quote over the
wrong PCRs and a wrong boot state — each by name. The
appliance's WireGuard public key is hashed INTO the
quote's extraData together with the hub's nonce, so one
signature covers freshness, the key binding and the boot
state; split them and a relayed quote proves some machine
is healthy while the attacker supplies the key. Nothing
the verifier compares against comes out of the blob.
Measure half done too: PCR 11 carries the merged
/proc/cmdline and the four ESP trust records, ABSENCE
INCLUDED. Named limit: the AK is not EK-certified, so a
hub's belief that it lives in a real chip is trust on
first sight — see ATTACK 2 below.
PCR-4 binding CLOSED — edda e26790b. The first boot that opens the
(closed) install-time blob re-seals it to sha256:0,4,7 and
atomically replaces it; never mints, so the registered
public key does not change. MEASURED first, in KVM: PCR 4
is identical across three boots of one loader and
different for another, and PCR 0/7 do not move with the
loader. Cost, named: a loader upgrade needs
KORP_RESEAL_PCRS=off on the last boot of the old one.
⏸ PARKED — Rickard's call, 2026-08-16: "låt data vara"
ATTACK 3b disk encryption everything other than the identity key stays plaintext
for /data at rest. Deliberate, not forgotten.
```
**One thing this board must not be read as saying.** A row marked CLOSED closed the
*mechanism*; it does not mean every product is configured to use it.
The sharpest example is gunnar right now. ATTACK 1 is closed, and gunnar's medium
nevertheless bakes `korp.require_signed=0`, so the appliance boots on blake3 integrity
alone — verified in the serial 2026-08-16:
```
INSTALLER: policy: require_signed=NO — this appliance is DECLARED unenrolled on the
SIGNED command line (korp.require_signed=0, korp.cmdline=baked) and boots on blake3
integrity alone. Integrity says the payload is the one that was pinned; only the
ed25519 anchor says WHO pinned it
```
That is exactly what the fix was for: the waiver is now a **declaration on the signed
half**, not the absence of a token an attacker can delete. But gunnar does not yet have
an enrolled root key, so gunnar does not yet have authenticity. Enrolling one and baking
`korp.require_signed=1` is the remaining product-side step, and it is not a code change.
---
## The chain as it actually runs
```
UEFI firmware
└─ BOOTX64.EFI = korp-boot SIGNED (Authenticode, isocompose.rs:170)
├─ reads \korp\cmdline ← A PLAIN FILE ON THE ESP. NOT SIGNED.
└─ LoadImage \vmlinuz SIGNED (isocompose.rs:172)
└─ korp-start (PID 1)
├─ /boot/korp/app.blake3 pin ESP, not signed
├─ /boot/korp/app.sig signature ESP, not signed
├─ /boot/korp/root.pub pubkey ESP, not signed
├─ selftest: 3 legs (archive blake3 · ed25519 over root · znippy chunks)
└─ memfd + fexecve the payload never lands on disk
```
`secureboot.rs` contains **zero** references to the command line; `sign_pe` is called
on exactly two things, and `\korp\cmdline` is not one of them.
## What is genuinely good, and better than the docs claim
```
payload-at-rest REAL two independent blake3 checks per boot — the pin over the
whole archive (korp-start), and znippy's per-chunk verify
during extraction. Re-run every boot, so tamper is
continuously evident, not just at install.
never-on-disk REAL the uncompressed program is written into an anonymous
memfd and fexecve'd. No extracted binary to swap.
small-order keys CLOSED Ed25519RootVerifier::from_pubkey_bytes rejects is_weak().
Without it an enrolled key of 32 zero bytes would verify 64
zero bytes over ANY payload — a valid-looking key that
trusts everything. That is a real ed25519 footgun and it is
already shut.
malformed ≠ absent CLOSED a root.pub that is present but not 32 bytes HALTS. It does
not silently downgrade to no-anchor, which is the obvious
way to disable a check by corrupting it.
pure Rust REAL no mdadm, no sbsign, no openssl, no shell. GPT, FAT, md
RAID1 v1.0 superblocks, Authenticode and ISO-9660 are all
written by hand. A smaller trusted surface than any
installer that shells out.
```
## ATTACK 1 — cmdline downgrade. The one that matters.
**Threat**: an attacker with write access to the ESP (evil maid, a stolen disk, a
recovery shell, any pre-boot code) — exactly the threat Secure Boot exists to stop.
**The move**: delete the `korp.require_signed=1` token from `\korp\cmdline`, delete
`root.pub` and `app.sig`, write your own payload to P2, write its blake3 into
`app.blake3`.
**Why it works**: policy is read from the same unsigned file the attack edits.
```rust
// korp-start.rs:94
let require_signed = std::env::var("KORP_REQUIRE_SIGNED")… || cl.require_signed;
// korp-start.rs:174
let checked = if require_signed { verdict.require_all_legs() } else { verdict.into_result() };
```
Remove the token and `require_signed` is false, so nothing is required. The signature
leg reports `NOT-ENROLLED` — honestly, loudly, and then boots anyway, because
integrity-only is a supported configuration and the machine no longer has anything
saying it should not be. `require_signature_leg` catches a *stripped record on an
enrolled machine*; it cannot catch a machine that has been told it was never enrolled.
The blake3 pin does not help: it is on the same partition, and the attacker rewrites
it. The code says this itself in `trust.rs:57` — *"a blake3 pin on a plaintext ESP that
anyone who rewrites the ESP can forge"*.
**This is precisely why UKI exists.** Embedding kernel + initrd + cmdline in one signed
PE means the command line cannot be changed without breaking the signature; a cmdline
kept as a separate ESP file is the textbook vector (`init=/bin/sh`, disable LSMs, change
the root device). korp-boot reads `\korp\cmdline` as a file, so it has the pre-UKI
shape with a post-UKI threat model.
**Fix, in order of cost**: bake the cmdline into the signed first-stage PE (or into a
UKI) so `korp.require_signed` and `korp.entry` are covered by Authenticode; or, much
cheaper as a stopgap, make `require_signed` **default true** and require an explicit
signed opt-out, so removing a token fails closed instead of open.
Also riding the same unsigned file, same reasoning: `korp.entry=`, `korp.args=` (the
app's argv) and `korp.env.<K>=` (its envp). Those reach a *verified* binary, so the
payload cannot be swapped — but its argv and environment can be chosen by whoever holds
the ESP.
### ✔ CLOSED, same day — both halves
**Step 1, the default (edda `326617c`).** `korp.require_signed` is no longer a
`bool` that defaults false; it is a three-state
(`cmdline::SignaturePolicy`): no token ⇒ **required**, `=1` ⇒ required and said
so, `=0` ⇒ declared unenrolled. Only the exact `=0`/`=false` waives the anchor —
`=off`, `=no` and any typo stay closed, so no misspelling is a downgrade.
`cmdline::decide` is the single place the rule lives and it returns the sentence
the console prints, so the installer PID-1 and `korp-start` cannot reach
different verdicts from one input. `InstallPlan::require_signed` defaults `true`;
`isocompose` bakes `korp.require_signed=0` and warns loudly when no root key is
configured, so "this fleet ships unsigned" is a decision the BUILDER made and the
medium records.
RED, seen first: flipping the default turned 9 lib tests red with the same
refusal a stripped machine now gets. `tests/attack1_cmdline_policy.rs` then runs
the attack for real — compose an ESP through the production writer, blank the
token out of the FAT bytes (same length, so no filesystem tooling is needed),
read it back through `fatfs`, hand the recovered line to the **shipped**
`korp-start` with a real `.znippy` whose integrity legs both PASS:
```
INSTALLER: policy: require_signed=YES (no korp.require_signed= token — the default is
CLOSED, so a REMOVED policy token refuses instead of downgrading; …)
INSTALLER: selftest startup root=6a8c2bb11c2a archive-root-blake3=PASS
signature-over-root=NOT-ENROLLED per-chunk-blake3=PASS
INSTALLER: FAILED policy: korp.require_signed is IN FORCE and the authenticity anchor
did not hold … An appliance that genuinely carries no enrolled key is a legal
configuration and must SAY so — declare korp.require_signed=0 on the medium that
composed it
```
**Step 2, the signature (edda `cbedd19`).** Whoever can delete a token can add
one, so the default alone was never enough. The authoritative token subset
(`cmdline::authoritative_tokens`: `korp.require_*`, `korp.entry=`, `korp.args=`,
`korp.env.*`, `korp.seal_pcrs=`, `korp.pin=`, `KORP_*`) is compiled INTO
`korp-boot` by its `build.rs` from `KORP_BAKED_CMDLINE`, which
`isocompose::compose_and_master` sets **before** it builds the loader and
therefore before it Authenticode-signs it. Resolving the command line is now
step 0 of the composition; it used to be step 4, after the loader was built and
signed, which is the one order that cannot work.
`\korp\cmdline` survives, subordinate. `korp-boot` merges it UNDER the baked
half and drops any token whose key is baked, any `korp.require_*`, any `KORP_`
(Linux turns an unrecognised `KEY=value` into init's ENVIRONMENT, and
`korp-start` reads `KORP_CMDLINE`/`KORP_APP_PIN`/`KORP_APP_SIG`/
`KORP_ROOT_PUBKEY` from exactly there — without this an attacker skips the whole
mechanism with one token), and `korp.cmdline`. Then it stamps
`korp.cmdline=baked`, which the ESP can never write for itself, and `korp-start`
honours `korp.require_signed=0` **only** on a line carrying that stamp.
NOT baked, deliberately: `console=`, `korp.appliance=`, `korp.znippy=`,
`initrd=` and the products' own boot params. Those are per-MACHINE plumbing an
installed disk legitimately rewrites — its app is a RAW PARTITION the ISO knows
nothing about — and a loader that insisted on its own copy would brick every
install. They are also not what this attack is about: pointing the loader at
another archive changes nothing while the pin and the anchor still have to hold.
RED, on applied output — the chainloaded PE's own echo of the `LoadOptions` it
was handed, against an ESP carrying the full evil-maid set:
```
KORP-BOOT: cmdline (\korp\cmdline, UNSIGNED - subordinate) = "console=ttyS0
korp.znippy=part:korp-rootfs korp.require_signed=0
KORP_CMDLINE=korp.require_signed=0 korp.env.BAKEDNONCE=c9e23e2f29c1df1e initrd=\initrd"
KORP-BOOT: cmdline BAKED (signed, authoritative) = "korp.require_signed=1
korp.entry=korp korp.env.BAKEDNONCE=18c8f57793c46d26"
KORP-STAGE2: ALIVE cmdline="console=ttyS0 korp.znippy=part:korp-rootfs initrd=\initrd
korp.require_signed=1 korp.entry=korp korp.env.BAKEDNONCE=18c8f57793c46d26
korp.cmdline=baked"
```
and the signature half, one byte of the baked `korp.require_signed=` flipped
`1`→`0` inside the **signed** image (same length, so only content changed),
under Secure-Boot-enforcing OVMF, against a control boot of the untouched image
that runs and hands the baked policy on:
```
BdsDxe: failed to load Boot0002 "UEFI Misc Device" …: Access Denied
-- rejected probably by Secure Boot
```
**What is still open.** A machine whose `korp-boot` bakes nothing — an old
medium or a hand-laid ESP — has no authoritative half to merge under, so its
whole command line is attacker-choosable. It cannot be downgraded (a waiver
without the stamp is refused by name and told to recompose), but its
`korp.znippy=`, `console=` and `initrd=` are still whoever's holds the disk.
And on a BAKED machine the subordinate half remains exactly that: the loader can
be pointed at a different archive, which the pin and the anchor then have to
catch — as they always did.
## ATTACK 2 — no measured boot, no sealing, no attestation
**Grepped, not assumed** (2026-08-16, `\btpm\b|tpm2|pcr[0-9]|pcr_|measured.?boot|tss2|swtpm`
over `*.rs`/`*.toml`/`*.md`, `target/` and `vendor/` excluded):
```
korp 0 holger 0 gunnar 1 (an unrelated doc line)
tunnr 0 draupnir 0 Skidbladnir 0 dwarves 0
edda 18 ← every one of them accounted for below
```
edda's eighteen are: this file, `nornir-vault`'s `TpmProvider` — its own doc says
**"design-only stub … TODO: pick a portable TPM 2.0 binding"**, and it is the
*thin-client* vault (korp's own mTLS cert / Spark token), not the appliance —, one
`banner.rs` comment using "measured-boot" as prose about console ordering, and two lines
in a `parked-gix/` scratch doc.
**`korp-installer` itself: zero.** So:
* nothing is measured into PCRs, so the platform cannot **prove** its state to a hub —
the appliance's WireGuard identity is asserted, never attested;
* no secret can be **sealed** to a boot state, so there is no way to make a key
unavailable to a tampered boot;
* Secure Boot alone blocks some bad code but leaves you blind afterwards, which is the
standard reason measured boot is paired with it.
### ✔ HALF CLOSED, same day — `korp_installer::tpm` + `korp_installer::wgseal`
**Sealing exists now, and it is measured.** A pure-Rust TPM 2.0 client
(`crates/korp-installer/src/tpm.rs`, hand-marshalled over `/dev/tpmrm0` — no
`tss2`, no shell, no FFI) carries `StartAuthSession`/`PolicyPCR`/
`PolicyGetDigest`/`CreatePrimary`/`Create`/`Load`/`Unseal`/`FlushContext`/
`PCR_Read`/`PCR_Extend`/`PCR_Reset`/`GetRandom`. Proven against **oden's real
chip** and against two independent `swtpm` instances:
```
PCR_Read vs /sys/class/tpm/tpm0/pcr-sha256/* 16 of 16 agree byte-for-byte
seal (ECC-P256 SRK + PCR policy) 212 ms blob 288 B on disk
unseal 648 ms byte-identical
```
What is still MISSING from ATTACK 2 proper: nothing is *measured into* PCRs by
korp's own chain (the firmware measures itself; korp-boot measures nothing), and
there is still no attestation to a hub. Sealing is the confidentiality half;
attestation is the other half and is untouched.
### ✔ CLOSED, same day — `korp_installer::attest` + `korp_installer::measure`
**The hub can now check the claim it used to have to take.** `TPM2_Quote` is
implemented in the same hand-marshalled client (no `tss2`, no shell), signed by
a deterministic ECC-P256 **restricted** primary on the owner hierarchy — the SRK
template with `sign` where the SRK has `decrypt`. Restricted is the bit that
matters: the chip refuses to sign external data beginning with
`TPM_GENERATED_VALUE`, so a quote cannot be a blob somebody handed the chip.
**The seam is one hash.** `extraData = blake3(domain ‖ hub nonce ‖ wg_public_b64)`,
so a single signature covers three things at once:
```
freshness the hub's own 32-byte nonce is inside the signed bytes
identity binding the appliance's WireGuard public key is inside the SAME bytes
boot state the PCR digest
```
Split them and each is defeatable: a quote carrying only a nonce proves *some*
machine is healthy, and the attacker relays it beside their own key. That exact
relay is a red in the suite, on real silicon, and it is refused as
`STALE or MISDIRECTED quote`.
**Nothing the verifier compares against comes out of the blob.** The nonce is
the one the hub issued, the WireGuard key is the one it has on file, the AK is
the point pinned at enrolment, the expected boot state is its own record. A
verifier that read the nonce out of the attestation would accept every replay
and still be green.
**And korp now measures its own chain.** The firmware measures every image it
*loads* into PCR 4 — measured, not assumed: three boots of one loader give an
identical PCR 4 and a different loader gives a different one — so korp-boot, the
kernel it chainloads, and (since `cbedd19`) the baked policy tokens inside the
signed PE are already covered. What was not covered is what korp-start *reads*.
PCR **11** now carries, once per boot:
```
/proc/cmdline (merged) the line the KERNEL received — its ESP third is unsigned
korp/app.blake3 the payload pin
korp/app.sig the detached signature
korp/root.pub the enrolled anchor
korp/rollback.floor the version floor
```
**Absence is measured**, which is the entire point and is ATTACK 1's residue:
delete `root.pub` and `app.sig` and the machine honestly reports `NOT-ENROLLED`
and boots, because from the inside a machine that was never enrolled and one
whose anchor was deleted this morning are identical. They are not identical to
a hub holding the golden value — but only if a missing file hashes to something
rather than being skipped.
The seal deliberately does **not** bind to PCR 11: the ESP third of the command
line legitimately differs per machine, and binding the identity to the payload
pin would cost a machine its key on every app update. The seal binds to what
must be stable; the quote reports everything.
**The named limit, and it is the real one.** The AK is not certified against the
TPM's endorsement key (no `MakeCredential`/`ActivateCredential`), so the hub's
belief that the AK lives in *a genuine TPM* is **trust on first sight**: it
learns the point in the same operator-carried ceremony that hands it the
WireGuard public key. Every quote afterwards is a real statement — *the same
machine as at enrolment, in this boot state now* — which is precisely what was
missing. Detecting a forged FIRST enrolment needs the EK chain and is a separate
change. An EK-hierarchy AK would also report the chip's true counters; ours are
obfuscated by the spec (oden reports `reset #3622688011`), so they are
comparable across quotes from one machine and meaningless in absolute terms.
## ATTACK 3 — the minted WireGuard key is plaintext at rest, and it was meant to be TPM-sealed
**This one is not a missing nice-to-have. It is stated design intent that was never
built, and nothing in the tree says so.**
The ceremony mints the appliance's WireGuard private key onto P3 and protects it with
`0600` plus an `O_CREAT|O_EXCL` refusal on a group-readable file. That is all. Grepped
`wg-appliance-core` for `tpm|seal|encrypt|luks|aes|kek`: the only hits are
`hubfixture.rs`'s `sealed_packets`, which counts WireGuard datagrams on the wire and has
nothing to do with rest. `ceremony.rs:55` states the shape plainly:
> *"The appliance's private key, base64 + newline, mode 0600. The file IS the …"*
Base64. On an unencrypted partition. OS permissions are the whole protection, and they
protect nothing against someone holding the disk: pull the drive, read the file, and you
**are** that appliance to the hub — no forgery needed, it is the real key. The hub cannot
tell the difference, because there is no attestation (ATTACK 2) for it to check against.
The step that made this reachable is in the design notes as a *fix*: `/data` had to
become persistent so the key could survive reboot, because a RAM-backed `/data` could
not keep one. That succeeded. It also turned a key that evaporated on power loss into a
durable secret, and the confidentiality half was never added.
**What the design called for**: mint the key, then seal it to the platform TPM under a
PCR policy, so it unseals only on a boot that measures the same. Then a stolen disk is
inert and a tampered boot cannot reach the key.
**What is on the box today**: the key in the clear, and a `TpmProvider` in a different
crate that returns an error saying it is design-only.
**Cheapest honest step if a full seal is far off**: say so in `ceremony.rs`. Right now
the module documents the `0600` and the `O_EXCL` in loving detail, which reads as
*this key is looked after* — and it is looked after against the wrong adversary.
### ✔ CLOSED, same day — the installer mints it and the TPM keeps it
Rickard's ruling: *the installer generates the key, and it is stored on the disk,
alongside the znippy archive.* Built:
* **`korp-installer` mints and seals** (`install.rs` → `wgseal::mint_and_seal`).
The private half goes into the chip and lands on P3 as `wg/identity.sea`,
wrapped to this TPM under a PCR policy. The **public** half goes on the console
in the canonical `wg-appliance identity:` line the fleet's one watcher already
greps, so the operator pastes it into the hub's `[Peer]` stanza and **nothing
secret leaves the machine**. Steps 3+4 of the ceremony, which
`nornir-workspace.toml` still marked `planerat`.
* **`korp-start` unseals** (`datahome::boot_data_home` → `wgseal::
load_or_mint_sealed_identity`). Idempotency is kept: a machine with an identity
never re-mints.
* **Fail closed, by name.** A machine carrying `wg/identity.sea` that cannot open
it REFUSES — no reachable TPM, a moved PCR, a corrupt blob, all `FAILED unseal:`
with the chip's own `TPM_RC` and the live PCR values on the line. It never mints
a plaintext replacement, because that would turn the strongest state into the
weakest one on exactly the boot where something is wrong. A `wg/` holding BOTH a
seal and a plaintext key is refused as an ambiguity.
* **Declared, not downgraded.** A machine with no TPM says so every boot, names
what it could not seal to, and points at `korp.require_sealed=1` — which turns
the whole configuration into a refusal at install time and at boot time.
The bound PCRs are `sha256:0,7` (`korp.seal_pcrs=` overrides), and that choice is
**measured, not reasoned** — `tests/tpm_kvm_pcrs.rs` boots OVMF twice against one
emulated chip:
```
from a CD-ROM from a hard disk
PCR 0 012103E0F956C315… 012103E0F956C315… SAME
PCR 1 91FE52953A8C078E… A339966DDE8F6FE2… DIFFERS ← the finding
PCR 7 B5710BF57D25623E… B5710BF57D25623E… SAME
```
PCR 1 is the platform *configuration* PCR and the attached media is part of that
configuration — so the binding that reads like the careful choice, "PCRs 0–7",
would install every appliance cleanly and then break it on its first boot.
**What is still plaintext, said plainly**: `hub.conf` (correctly — public data)
and the `.znippy` payload (correctly — it is an executable carrying no data,
already covered by blake3 + ed25519).
#### ✔ The PCR-4 gap, CLOSED — edda e26790b
The seal used to bind firmware + Secure Boot state and **not which loader ran**,
so a substituted korp-boot still opened the appliance's key. The installer
cannot close that — its own PCR 4 measures the *installer's* loader — so the
first boot that successfully opens the blob re-seals it to `sha256:0,4,7`.
Measured before it was built, because widening a PCR set on reasoning is how you
ship an appliance that opens its key exactly once (`tests/tpm_kvm_pcrs.rs`, four
OVMF boots off disks this installer laid down):
```
loader A, boot 1 PCR 4 = F2A242EDDC7012A9…
loader A, boot 2 PCR 4 = F2A242EDDC7012A9… STABLE
loader A, boot 3 PCR 4 = F2A242EDDC7012A9… STABLE
loader B PCR 4 = 9ED54B67D7E73E6A… DIFFERS
PCR 0 / PCR 7 unchanged by the loader swap
```
Both halves had to hold. Unstable across reboots and every appliance refuses its
own key on the boot after the re-seal; identical across loaders and the binding
costs the fragility and buys nothing. The third boot is there because a value
that settled on boot 2 and moved on boot 3 would look correct and brick the
machine one reboot later than anyone thinks to look.
The commit protocol is six steps with **one rule** — `identity.sea` is
authoritative whenever it exists, `identity.new` is adopted only when it does
not — so every crash window leaves at least one openable copy of the same key.
Step 3 opens the staged blob again *through the chip* before the old one is
deleted; without it a machine can delete the blob it could open and commit one
it cannot, and find out on the next boot with nothing to fall back on. A re-seal
never mints, so the public key the hub registered does not change.
Proven on oden's real chip, including the part that is the point: with the
loader PCR moved, the re-sealed blob refuses with `TPM_RC_POLICY_FAIL` **and the
pre-re-seal blob still opens** — the gap, demonstrated rather than argued.
**The cost, named**: a loader upgrade now needs a deliberate step. Relax the
binding with `KORP_RESEAL_PCRS=off` on the last boot of the old loader (while
the machine can still open its key), swap it, and the first boot of the new one
re-binds. The refusal text says exactly this, because an operator meeting it for
the first time is meeting it on a machine that will not boot.
## ATTACK 4 — rollback ✔ CLOSED AGAINST REPLAY (edda cc370aa · 421cb7b · 94ca4bb)
## live forgery of the floor record is NOT closed — see below
`Ed25519RootVerifier::verify_root` signed the blake3 root and nothing else — no version,
no monotonic counter. A payload signed by the genuine key **stayed valid forever**, so an
attacker who kept an old, signed, vulnerable image could install it and every leg went
PASS, honestly, because every leg was true. The image really was whole and really was
ours. It was simply old, and nothing in the chain could say so. This is the case Android
Verified Boot spends a rollback index on.
### What is signed now
```
rollback::signed_record — fixed width, fixed order, no separators to disagree about
b"korp.root-of-trust.v1\0" 22 domain tag
version.to_be_bytes() 8 u64 big-endian, >= 1
root.as_bytes() 32 the blake3 root
----
62
```
The tag is the mechanism, not decoration. Without it the two formats would be "32 bytes"
and "40-ish bytes", and any code path willing to try both would let an attacker present
one as the other — downgrade by format confusion, reopening the hole through the fix that
closed it. Which format is in play is decided by the `app.sig` **framing** — 64 bytes = the
legacy bare signature, `KORPSIG1 ‖ u64 version ‖ sig` = 80 bytes = a versioned record —
**before a key is touched**, and a failed verify is never retried under the other reading.
The version is inside the signed material, so editing it in the blob breaks the signature.
The number the floor compares is a number the signing key stated.
### The floor — TWO homes, and a machine says which it has
`korp/rollback.floor`, read before the archive is touched, advanced only after every leg
held and only to a version a verify actually recovered. `/boot` is mounted `MS_RDONLY`, so
the write flips that mount rw and straight back; without that the floor would never leave
0 and the whole check would be a guard that can never fire.
It comes in two strengths, and `korp-start` prints which on every boot. **A floor of 31
anyone can rewrite and a floor of 31 the chip stands behind are different machines, and no
reader should have to guess which one they are looking at.**
```
esp-file:/boot/korp/rollback.floor (UNWITNESSED) no TPM
/boot/korp/rollback.floor witnessed by
tpm-nv:0x01800f20@/dev/tpmrm0 a real chip
```
**Witnessed (`rollback::TpmNvFloor`, edda 421cb7b).** A TPM NV counter cannot BE the
floor: it only counts, `NV_Increment` moves it by one, and it starts wherever that chip's
counters happen to start. Measured on oden's own chip — **7** for one freshly defined
index and **14** for another an hour later. Read that as a version and the machine refuses
every payload below 14 while looking exactly like working rollback protection.
So the counter is a **ticket**. The record carries both numbers — `31 4711` is "version 31,
witnessed by counter 4711" — and a boot refuses unless they still agree:
```
accept(N): write {N, V+1} then increment → V+1
boot: ticket == V consistent
ticket == V + 1 the write landed, the increment did not (power loss)
— complete it and carry on
ticket < V REFUSED: older than the chip
ticket > V + 1 REFUSED: never written here
```
Write-then-increment, and the healing window is one step **ahead**, which replay can never
reach because replay only ever moves a ticket backwards.
That closes the attack this page is written about: **an attacker who kept an old, properly
signed, vulnerable image** and restores it together with the floor that was current when
it shipped. Rewriting the ESP no longer erases the floor, because the number that
invalidates the record lives in the chip. Real-silicon refusal, `/dev/tpmrm0`:
```
rollback refused: the floor record at …/korp/rollback.floor claims version 900
witnessed by counter 16, and the NV counter at tpm-nv:0x01800f21@/dev/tpmrm0 is at
17 — a counter only ever goes UP, so this record is not the one this machine wrote.
An older record restored from a backup looks exactly like this, and it is the attack
the witness exists to catch
```
**What is still open, and say it rather than let "TPM-backed" imply it:** an attacker
present *now* can read the current counter and author `{version: 1, ticket: V}`. Detecting
that needs the floor VALUE inside the chip — `TPM2_NV_Write` against a policy-write index,
and `tpm.rs` has DefineSpace / Increment / Read / ReadPublic and **no Write** — or a MAC
over the record under a PCR-sealed key. **Replay is closed; live forgery is not.**
**Unwitnessed (`rollback::FloorFile`).** An appliance with no chip keeps the bare decimal
on the plaintext ESP, and **an attacker who can rewrite the ESP rewrites the floor.** That
is the same attacker ATTACK 1 describes. It still buys: an old genuinely-signed payload
swapped onto P2 is refused at the next boot; a stale update channel is refused instead of
accepted; a versioned machine never accepts an unversioned payload again. It does not
survive a re-install, which rewrites the ESP and the floor with it.
A machine that has ever had a **witnessed** record refuses the unwitnessed path by name,
so pulling the TPM — or booting a kernel without the driver — is not a way to disarm this.
`InstallPlan::rollback_floor` is where an installer hands a durable floor to the install
stage.
### The declared compatibility policy
An unversioned signature counts as version 0. On a machine whose floor is still 0 it is
**accepted** — nothing that boots today stops booting — and the `rollback-floor` leg
reports `NOT-ENROLLED`, never `PASS`, because such a machine has no rollback protection
and the serial line must not imply it does. Once any versioned payload has been accepted,
an unversioned one is **refused by name**. The fleet ratchets forward and never back.
**Same-version is accepted**: a repair, or a second boot of the running image, must not be
a brick. The forge signs versioned by DEFAULT (`KORP_PAYLOAD_VERSION`, default 1); opting
out is `KORP_PAYLOAD_VERSION=legacy` and has to be typed.
### The refusal
```
selftest startup root=… archive-root-blake3=PASS signature-over-root=PASS
rollback-floor=FAIL per-chunk-blake3=NOT-RUN
rollback refused: this payload is version 9 and this machine has already accepted
version 31 — an OLDER image with a genuine signature is exactly what this floor
exists to stop
```
The signature leg still says PASS beside it, which is the point: the image is ours, and
old.
## ATTACK 5 — a hostile pin file panicked PID 1 ✔ FIXED TODAY
`decode_hex32` checked `hex.len()` (bytes) and then sliced `&hex[i*2..i*2+2]` (a `str`
slice). `"a" + "é"×31 + "a"` is exactly 64 bytes and panics on the first pair — in PID 1,
where a panic is a kernel panic with no line naming the bad record, instead of the loud
refusal the design promises. Input is `/boot/korp/app.blake3` on the plaintext ESP.
Impact is denial of service only (a bad pin was always going to refuse), but it refused
in the wrong way. Fixed in `edda 1b95cd4`, red-proven first
(`end byte index 2 is not a char boundary; it is inside 'é'`).
## ATTACK 6 — media parsing, and why it is not the soft spot
`iso9660.rs` parses attacker-shaped structures off removable media. It bounds-checks:
`u32le` refuses a field that runs past its record, directory records that overrun their
extent are named and refused, and multi-extent files are refused rather than mis-joined.
Rust's slice indexing removes the class that would otherwise dominate here. Nothing
found worth fixing; the risk sits at ATTACK 1, not in the parsers.
## Honest scorecard against 2026 practice
```
signed boot chain DONE Authenticode over korp-boot + kernel, customer PK/KEK/db,
proven under enforcing OVMF (unsigned → Access Denied,
foreign key → Access Denied)
payload integrity DONE blake3, twice, every boot
payload authenticity OPTIONAL ed25519 over the blake3 root, real — but off unless
a policy token on an unsigned file says otherwise
signed command line DONE the UKI property, by baking: the policy, korp.entry=,
korp.args= and korp.env.* live inside the
Authenticode-signed korp-boot, \korp\cmdline is
merged under them and may never loosen. Red-proven
both ways (a hostile ESP ignored; a one-byte edit of
the baked line → Access Denied). ATTACK 1.
measured boot / PCRs DONE the FIRMWARE measures itself and every image it LOADS
(PCR 4 = the loaded PE, measured across four KVM boots);
korp extends PCR 11 with the merged /proc/cmdline and its
four ESP trust records, absence included
TPM-sealed secrets DONE the appliance identity key, sealed under sha256:0,7 by
the installer, unsealed by korp-start, fail-closed by
name. Pure Rust, no tss2. Red-proven on real silicon:
a moved PCR → TPM_RC_POLICY_FAIL, a foreign chip or an
edited blob → TPM_RC_INTEGRITY
disk encryption PARKED everything OTHER than the identity key is plaintext at
rest — correctly for hub.conf and the payload. Rickard's
call 2026-08-16: leave /data alone for now
dm-verity / RO root N/A the payload is a blake3-verified archive exec'd from
RAM, which covers the same ground differently
rollback protection DONE a u64 version is bound into the signed material
(with a (62-byte domain-separated record) and a lower version is
named refused by name at both install and boot. WITH A TPM the
limit) floor record is witnessed by an NV monotonic counter, so
restoring an old floor beside an old signed image is
refused — proven on oden's chip. WITHOUT one it is a
plaintext file and whoever rewrites the ESP rewrites it;
korp-start prints which of the two a machine has, every
boot. Live forgery by an attacker who reads the current
counter is NOT closed: that needs TPM2_NV_Write on a
policy-write index, or a PCR-sealed MAC over the record
A/B slots, atomic update MISSING not picked
remote attestation DONE TPM2_Quote, ECC-P256 restricted AK, hub-supplied nonce
AND the appliance's WireGuard public key hashed into one
extraData. Verifier refuses stale/foreign/forged/wrong-PCR
/wrong-state by name. Limit: the AK is not EK-certified,
so first enrolment is trust on first sight in flight — see the status board
per-product boot mark DONE an [[build.installer]] cell names its own boot-logo; the
chain runs boot-logo → KORP_BOOT_LOGO → korp-boot-efi's
build.rs → gop_splash, blitted before the first ESP read.
Before today InstallerBuild had no such field and the
production korp-boot had no splash code at all — the only
thing in the tree that drew a logo was a KVM test
fixture, so every product booted korp's ravens. Proven on
bytes (one loader, two builds: 743 048 B vs 596 968 B,
different md5) and on pixels (three distinct blake3 over
real screenshots), on the install boot AND on the reboot
```
**Is it "ultimate"? No, and it is not done.** What exists is unusually strong for its
size and unusually honest about what it has not measured. The gap between here and the
state of the art is one shape, not ten features: **bind the policy and the command line
into something signed, then measure the boot into a TPM so the machine can prove what it
ran.** ATTACK 1 was the only one on this page exploitable today by someone who
has already reached the disk, and it is the cheapest to close.
## Sources for the comparison
* <https://uapi-group.org/specifications/specs/unified_kernel_image/>
* <https://wiki.archlinux.org/title/Unified_kernel_image>
* <https://www.talos.dev/v1.10/talos-guides/install/bare-metal-platforms/secureboot/>
* <https://0pointer.net/blog/brave-new-trusted-boot-world.html>
* <https://source.android.com/docs/security/features/verifiedboot/avb>
* <https://source.android.com/docs/security/features/verifiedboot/dm-verity>