Skip to content

Commit 465e1f5

Browse files
committed
initrd: fix TPM1 counter auth regression (#2068)
PR #2068 introduced a regression where `increment_tpm_counter` was changed from hardcoded `-pwdc ''` (empty counter auth per TCG spec) to `-pwdc "${tpm_passphrase:-}"` (owner passphrase), while counters continued to be created with `-pwdc ''`. This caused every increment to compute SHA1(owner_pass) against a counter created with SHA1(""), producing persistent TPM_AUTHFAIL. Per TCG TPM Main Spec Part 3, TPM_CreateCounter uses owner auth (-pwdo) but TPM_IncrementCounter uses the counter's own authData — not the owner password. The correct design for Heads' rollback counter is empty auth. The repeated auth failures (3 per boot) accumulated the TPM's dictionary attack (DA) failedTries counter until lockout was reached (~10 boots = 30 failures). Users reported "hours of waiting" on affected hardware. On some implementations the DA state persisted through tpm forceclear. Fix: restore TCG-compliant empty counter auth: - tpm1_counter_increment: detect explicit -pwdc '' and call tpm directly, bypassing _tpm_auth_retry. Non-empty or absent -pwdc falls through to owner-auth retry path for migration of counters created by pre-fix code. - check_tpm_counter: create counters with -pwdc '' instead of owner passphrase. - increment_tpm_counter: increment with -pwdc '' instead of owner passphrase; counter_create fallback uses empty auth. - oem-factory-reset.sh: create counters with -pwdc ''. Signed-off-by: Thierry Laurion <insurgo@riseup.net>
1 parent 7d3a28a commit 465e1f5

4 files changed

Lines changed: 66 additions & 29 deletions

File tree

doc/tpm.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,37 @@ counter passes preflight or the user chooses to continue.
330330
exit status of `tee` (always 0), not `tpmr.sh`. See
331331
[ux-patterns.md](ux-patterns.md#tpm-counter-patterns) for the correct pattern.
332332

333+
### TPM1 counter auth
334+
335+
TPM1 rollback counters use **empty auth** (`SHA1("")`) per the TCG
336+
specification (TPM Main Spec Part 3: `TPM_CreateCounter` uses owner
337+
auth `-pwdo`, but `TPM_IncrementCounter` uses the counter's own
338+
authData — not the owner password).
339+
340+
A regression in PR #2068 changed `increment_tpm_counter` from
341+
hardcoded `-pwdc ''` to `-pwdc "${tpm_passphrase:-}"` (owner
342+
passphrase), while counters continued to be created with `-pwdc ''`.
343+
This caused every increment to compute SHA1(owner_pass) against a
344+
counter created with SHA1(""), producing persistent `TPM_AUTHFAIL`.
345+
346+
The repeated auth failures (3 per boot) accumulated the TPM's
347+
dictionary attack (DA) failedTries counter until lockout was reached
348+
(~10 boots = 30 failures). Users reported "hours of waiting" on
349+
affected hardware. On some implementations, the DA state persisted
350+
through `tpm forceclear`.
351+
352+
The fix (PR #2117):
353+
- `tpm1_counter_increment`: detect explicit `-pwdc ''` and call
354+
`tpm counter_increment` directly, bypassing `_tpm_auth_retry`.
355+
Non-empty `-pwdc` or absent flag falls through to the owner-auth
356+
retry path for migration of counters created by pre-fix code.
357+
- `check_tpm_counter` and `increment_tpm_counter`: create and
358+
increment counters with `-pwdc ''` instead of owner passphrase.
359+
- `oem-factory-reset.sh`: uses `-pwdc ''` for counter creation.
360+
361+
TPM2 counters are unaffected — they still require owner auth for
362+
`tpm2 nvincrement`.
363+
333364
---
334365

335366
## TPM secret sealing internals (TPM2)

initrd/bin/oem-factory-reset.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ generate_checksums() {
868868
if [ "$CONFIG_TPM" = "y" ]; then
869869
if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then
870870
tpmr.sh counter_create \
871-
-pwdc "${TPM_PASS:-}" \
871+
-pwdc '' \
872872
-la -3135106223 |
873873
tee /tmp/counter >/dev/null 2>&1 ||
874874
whiptail_error_die "Unable to create TPM counter"

initrd/bin/tpmr.sh

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,8 @@ _tpm_auth_retry() {
429429
}
430430

431431
# tpm1_counter_create - Create a TPM1 rollback counter.
432-
# Delegates to _tpm_auth_retry for passphrase-driven retry on auth failure.
432+
# Owner passphrase (via _tpm_auth_retry -pwdo) is required for NV_DefineSpace.
433+
# Callers pass -pwdc '' for empty counter auth per TCG spec.
433434
tpm1_counter_create() {
434435
TRACE_FUNC
435436
_tpm_auth_retry "counter_create" "stdout" "tpm1" "-pwdo" tpm counter_create "$@"
@@ -443,17 +444,35 @@ tpm1_counter_read() {
443444

444445
# tpm1_counter_increment - Increment a TPM1 rollback counter.
445446
# Args: -ix <index> [ -pwdc <passphrase> ]
447+
#
448+
# Auth behaviour:
449+
# -pwdc '' : empty counter auth (SHA1 of ""), correct per TCG spec.
450+
# Calls tpm directly without retry — caller handles fallback.
451+
# -pwdc <pass> : owner passphrase auth via _tpm_auth_retry (migration).
452+
# (no -pwdc) : owner passphrase auth via _tpm_auth_retry (backward compat).
446453
tpm1_counter_increment() {
447454
TRACE_FUNC
448455
local counter_id=""
456+
local pwdc_provided="n"
457+
local pwdc_value=""
449458
while [ $# -gt 0 ]; do
450459
case "$1" in
451460
-ix) counter_id="$2"; shift 2 ;;
452-
-pwdc) shift 2 ;; # passphrase handled by _tpm_auth_retry
461+
-pwdc) pwdc_provided="y"; pwdc_value="$2"; shift 2 ;;
453462
*) shift ;;
454463
esac
455464
done
456-
_tpm_auth_retry "counter_increment" "stdout" "tpm1" "-pwdc" tpm counter_increment -ix "$counter_id"
465+
if [ "$pwdc_provided" = "y" ] && [ -z "$pwdc_value" ]; then
466+
# Empty counter auth per TCG spec: this is the normal counter
467+
# increment (no secret). Bypass _tpm_auth_retry because the
468+
# empty string is intentional, not a user input error.
469+
# Use || return so set -e doesn't kill the script on DA failure.
470+
DEBUG "tpm1_counter_increment: empty auth, calling tpm directly"
471+
tpm counter_increment -ix "$counter_id" -pwdc '' || return $?
472+
else
473+
_tpm_auth_retry "counter_increment" "stdout" "tpm1" "-pwdc" \
474+
tpm counter_increment -ix "$counter_id"
475+
fi
457476
}
458477

459478
tpm2_counter_create() {

initrd/etc/functions.sh

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,7 +1848,8 @@ check_tpm_counter() {
18481848
TRACE_FUNC
18491849

18501850
LABEL=${2:-3135106223}
1851-
tpm_passphrase="$3"
1851+
# $3 (tpm_passphrase) was used by pre-PR #2068 code but is now intentionally
1852+
# ignored — counters are created with empty auth (-pwdc '') per TCG spec.
18521853
# if the /boot.hashes file already exists, read the TPM counter ID
18531854
# from it.
18541855
if [ -r "$1" ]; then
@@ -1857,12 +1858,8 @@ check_tpm_counter() {
18571858
DEBUG "Extracted TPM_COUNTER: '$TPM_COUNTER' from $1"
18581859
else
18591860
DEBUG "$1 does not exist - creating new TPM counter"
1860-
# Warn user: TPM Owner Passphrase is required to create a new TPM counter
1861-
if [ ! -s /tmp/secret/tpm_owner_passphrase ]; then
1862-
WARN "TPM Owner Passphrase is required to create a new TPM counter for /boot content rollback prevention"
1863-
fi
1864-
1865-
# attempt to make a new counter, capturing any stderr for debugging
1861+
# Create TPM counter with empty counter auth per TCG spec (no secret).
1862+
# Owner passphrase is not needed for the counter auth itself.
18661863
DEBUG "Invoking tpmr.sh counter_create with label $LABEL"
18671864
# run it, then record the exit status explicitly; the '!' operator
18681865
# cannot be used because it would hide the real return code.
@@ -1872,7 +1869,7 @@ check_tpm_counter() {
18721869
(
18731870
set +e
18741871
tpmr.sh counter_create \
1875-
-pwdc "${tpm_passphrase:-}" \
1872+
-pwdc '' \
18761873
-la "$LABEL" \
18771874
>/tmp/counter 2> >(tee >(SINK_LOG "tpm counter_create stderr") >&2)
18781875
echo $? > /tmp/counter_create_rc
@@ -2050,22 +2047,11 @@ increment_tpm_counter() {
20502047
counter_present="y"
20512048
fi
20522049

2053-
# Prefer explicit passphrase, otherwise reuse cached TPM owner passphrase.
2050+
# TPM2 uses owner-auth fallback in tpm2_counter_inc; TPM1 uses empty counter
2051+
# auth (SHA1("")) per TCG spec — no owner passphrase needed for increment.
2052+
# Keep the cached owner passphrase for TPM2 fallback.
20542053
if [ -z "$tpm_passphrase" ] && [ -s /tmp/secret/tpm_owner_passphrase ]; then
20552054
tpm_passphrase="$(cat /tmp/secret/tpm_owner_passphrase)"
2056-
DEBUG "increment_tpm_counter: using cached TPM owner passphrase"
2057-
fi
2058-
2059-
# TPM1 counter_increment requires owner auth in practice on this path.
2060-
# origin/master typically reached this with cached owner passphrase already set,
2061-
# but the newer reseal/update flows can call this later in the session after
2062-
# that cache is absent. Prompt once and cache to avoid empty -pwdc failures.
2063-
if [ "$CONFIG_TPM2_TOOLS" != "y" ] && [ -z "$tpm_passphrase" ]; then
2064-
WARN "TPM Owner Passphrase is required to update rollback counter before signing updated boot hashes."
2065-
DEBUG "increment_tpm_counter: TPM1 path has no cached/provided owner passphrase; prompting now"
2066-
prompt_tpm_owner_password
2067-
tpm_passphrase="$tpm_owner_passphrase"
2068-
DEBUG "increment_tpm_counter: TPM1 owner passphrase obtained and cached"
20692055
fi
20702056

20712057
# Try to increment the counter. We normally hide the verbose
@@ -2094,7 +2080,7 @@ increment_tpm_counter() {
20942080
increment_ok="y"
20952081
fi
20962082
else
2097-
# TPM1 path uses owner auth in practice.
2083+
# TPM1 counter uses empty auth (SHA1 of "") per TCG spec.
20982084
# NOTE: tpmtotp C code prints ALL output (success + errors) to stdout.
20992085
# We must capture stdout to detect failures properly.
21002086
# DO_WITH_DEBUG internally captures the command's stderr (tee /dev/stderr
@@ -2104,7 +2090,7 @@ increment_tpm_counter() {
21042090
if (
21052091
set -o pipefail
21062092
DO_WITH_DEBUG --mask-position 5 \
2107-
tpmr.sh counter_increment -ix "$counter_id" -pwdc "${tpm_passphrase:-}" \
2093+
tpmr.sh counter_increment -ix "$counter_id" -pwdc '' \
21082094
2>/dev/null | tee /tmp/counter-"$counter_id" >/dev/null
21092095
); then
21102096
increment_ok="y"
@@ -2123,10 +2109,11 @@ increment_tpm_counter() {
21232109

21242110
# run counter_create but tee its stdout to a file so we still see
21252111
# the interactive prompt and any informational messages.
2112+
# Empty counter auth (-pwdc '') per TCG spec.
21262113
if (
21272114
set -o pipefail
21282115
DO_WITH_DEBUG --mask-position 3 \
2129-
tpmr.sh counter_create -pwdc "${tpm_passphrase:-}" -la 3135106223 \
2116+
tpmr.sh counter_create -pwdc '' -la 3135106223 \
21302117
2> >(tee >(SINK_LOG "tpm counter_create stderr") >&2) |
21312118
tee /tmp/new-counter >/dev/null
21322119
); then

0 commit comments

Comments
 (0)