Skip to content

Commit 891cf0c

Browse files
committed
Fix TPM counter increment: auth retry on wrong passphrase (TPM1+TPM2)
TPM1: counter_increment fell through to raw 'exec tpm' with no error handling — a mistyped owner passphrase killed the signing flow instantly. TPM2: counter_increment had no retry loop, unlike tpm2_seal and tpm2_counter_create which already re-prompted on auth failure. Changes: initrd/bin/tpmr.sh: - _tpm1_auth_retry: shared retry helper for TPM1 counter create/increment, re-prompts owner passphrase up to 3× on auth failure, reuses cached passphrase on first attempt (no double-prompt on happy path) - tpm1_counter_create / tpm1_counter_increment: thin wrappers around helper - tpm1_counter_read: named function (was catch-all exec tpm passthrough) - tpm1_seal: restore define+write retry loop removed by stdout-quirk fix, definespace output to /dev/null (ignored, nv_writevalue is the real test) - tpm2_counter_inc: add retry loop with should_retry flag to distinguish index-auth (-pwdc , no retry) from owner-auth (retry up to 3×) - All shred calls in retry loops: add 2>/dev/null || true guards - TPM1 dispatch: wire counter_read, counter_increment to named functions - TPM1/TPM2 extend/reset: improved PCR INFO messages initrd/bin/gui-init.sh: - reset_tpm: verify tpmr.sh reset exit code, show error and return to menu if reset fails (prevents operations on inconsistent TPM) initrd/etc/functions.sh: - increment_tpm_counter: replace misleading outer SINK_LOG with 2>/dev/null (DO_WITH_DEBUG already captures command stderr via SINK_LOG internally) - Document DO_WITH_DEBUG stderr handling in comments doc/tpm.md: document "out of resources" counter error and recovery flow doc/ux-patterns.md: UX patterns for TPM error recovery Signed-off-by: Thierry Laurion <insurgo@riseup.net>
1 parent bb8c6be commit 891cf0c

7 files changed

Lines changed: 375 additions & 103 deletions

File tree

doc/tpm.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,29 @@ Failure conditions and their diagnostic messages:
311311
| TPM2: counter has `ownerwrite` but not `authwrite` | "TPM counter has invalid security policy." |
312312
| TPM2: counter has neither `authwrite` nor `ownerwrite` | "TPM counter is not writable." |
313313
| TPM2: counter attributes empty or unreadable | "TPM counter policy is corrupted." |
314+
| TPM1: `counter_create` failed with "out of resources" (0x15) | "TPM has too many counters (out of resources). Reset the TPM from the GUI menu..." |
315+
316+
When `check_tpm_counter` calls `tpmr counter_create` and it fails, the function
317+
calls `set_tpm_reset_required` to create the `/tmp/secret/tpm_reset_required`
318+
marker. This gates `prompt_update_checksums` on subsequent invocations,
319+
preventing the user from retrying "sign /boot" until the TPM is reset.
320+
321+
The "out of resources" (TPM 1.2 error 0x15) case occurs when the TPM already
322+
has a counter from a prior firmware session but `/boot/kexec_rollback.txt` is
323+
missing (e.g. after a firmware reflash that changed the boot partition layout).
324+
The only safe recovery is to reset the TPM from the GUI menu, which clears all
325+
counters and creates a fresh one.
326+
327+
### TPM 1.2 stdout quirk (tpmtotp)
328+
329+
The tpmtotp C toolkit (`counter_create.c`, `unsealfile.c`, `sealfile2.c` and
330+
others) prints ALL output — both success and error messages — via `printf()` to
331+
**stdout**, NOT stderr. This is a quirk of the tpmtotp codebase.
332+
333+
All `tpm1_*` functions in `tpmr.sh` must therefore capture stdout (not just
334+
stderr) to detect failures. Use `>"$file" 2>&1` to capture both streams,
335+
and run TPM commands in subshells with `set +e` to avoid `set -e` killing the
336+
script on non-zero exit codes.
314337

315338
The exact diagnostic message from `fail_preflight` is shown directly in the
316339
error dialog — **not** a vague paraphrase. This tells the user and any support

doc/ux-patterns.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,26 @@ that seals new TPM secrets. It verifies:
186186
If either check fails, the user is shown an error and the sealing operation is aborted.
187187
This prevents new TOTP/HOTP/DUK secrets from being sealed against a potentially compromised `/boot`.
188188

189+
### Gate bypass for "Reset the TPM"
190+
191+
When the integrity gate fails specifically because `tpm_reset_required` is set (the
192+
TPM has stale counters that need clearing), the "Reset the TPM" option in both the
193+
TOTP failure whiptail menu and the TPM/TOTP/HOTP options whiptail menu uses a
194+
gate-bypass pattern to allow the reset to proceed:
195+
196+
```bash
197+
# If the gate failed *because* TPM reset is required (stale counters),
198+
# proceed to reset_tpm() which clears them and creates a fresh one.
199+
if { gate_reseal_with_integrity_report || tpm_reset_required; } && reset_tpm; then
200+
reseal_tpm_disk_decryption_key
201+
fi
202+
```
203+
204+
Without this bypass, selecting "Reset the TPM" triggers the integrity gate,
205+
which calls `check_tpm_counter` which hits "out of resources" and collapses
206+
the flow — the reset never executes. The `|| tpm_reset_required` lets the
207+
reset proceed when the gate failure is itself a symptom of needing the reset.
208+
189209
---
190210

191211
## GPG User PIN caching

initrd/bin/gui-init.sh

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,19 @@ prompt_update_checksums() {
191191
--yesno "You have chosen to update the checksums and sign all of the files in /boot.\n\nThis means that you trust that these files have not been tampered with.\n\nYou will need your GPG key available, and this change will modify your disk.\n\nDo you want to continue?" 0 80); then
192192
if update_checksums; then
193193
return 0
194+
fi
195+
# update_checksums may have set the TPM-reset-required marker
196+
# during its execution (e.g. check_tpm_counter hit "out of
197+
# resources"). Show the targeted TPM message instead of the
198+
# generic failure so the user knows exactly what to do.
199+
if tpm_reset_required; then
200+
whiptail_error --title 'TPM Reset Required' \
201+
--msgbox "Cannot sign /boot: TPM state is inconsistent.\n\nReset the TPM first (Options -> TPM/TOTP/HOTP Options -> Reset the TPM), then update checksums." 0 80
194202
else
195203
whiptail_error --title 'ERROR' \
196204
--msgbox "Failed to update checksums / sign default config" 0 80
197-
return 1
198205
fi
206+
return 1
199207
fi
200208
return 1
201209
}
@@ -411,8 +419,13 @@ EOF
411419
skip_to_menu="true"
412420
return 1
413421
;;
422+
# "Reset the TPM" from the TOTP failure whiptail menu.
423+
# The gate runs first to verify /boot integrity. If the gate
424+
# fails *because* TPM reset is required (e.g. stale counters),
425+
# the || tpm_reset_required bypass lets reset_tpm() proceed —
426+
# it clears counters and creates a fresh one.
414427
p)
415-
if gate_reseal_with_integrity_report && reset_tpm && update_totp && BG_COLOR_MAIN_MENU="normal"; then
428+
if { gate_reseal_with_integrity_report || tpm_reset_required; } && reset_tpm && update_totp && BG_COLOR_MAIN_MENU="normal"; then
416429
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
417430
fi
418431
;;
@@ -808,8 +821,11 @@ show_tpm_totp_hotp_options_menu() {
808821
update_totp && update_hotp || true
809822
fi
810823
;;
824+
# "Reset the TPM" from the TPM/TOTP/HOTP options whiptail menu.
825+
# Same gate-bypass pattern: if the gate fails because TPM
826+
# reset is required, proceed to reset_tpm() anyway.
811827
r)
812-
if gate_reseal_with_integrity_report && reset_tpm; then
828+
if { gate_reseal_with_integrity_report || tpm_reset_required; } && reset_tpm; then
813829
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
814830
fi
815831
;;
@@ -839,7 +855,18 @@ reset_tpm() {
839855
return 1
840856
fi
841857

842-
tpmr.sh reset "$tpm_owner_passphrase"
858+
# Verify TPM reset succeeded before proceeding to counter
859+
# creation, signing, TOTP generation, and DUK resealing.
860+
# A failed reset would leave the TPM in an inconsistent state
861+
# (old passphrase with unknown PCRs), causing confusing errors
862+
# downstream. Show the actual error to the user and return
863+
# to the menu.
864+
if ! tpmr.sh reset "$tpm_owner_passphrase" 2>/tmp/error; then
865+
ERROR=$(tail -n 1 /tmp/error | fold -s)
866+
whiptail_error --title 'ERROR' \
867+
--msgbox "Error resetting TPM:\n\n${ERROR}" 0 80
868+
return 1
869+
fi
843870

844871
# now that the TPM is reset, remove invalid TPM counter files
845872
mount_boot

0 commit comments

Comments
 (0)