Skip to content

Commit 56efdfd

Browse files
committed
initrd: fix TPM1 counter auth regression (#2068)
PR #2068 introduced a regression where TPM1 rollback counter operations passed the TPM owner passphrase as the counter auth (-pwdc) instead of empty auth (SHA1("")) per TCG spec. Impact: each boot triggered 3 TPM auth failures from counter create/increment operations. Over multiple boots this accumulated the TPM's dictionary attack (DA) failedTries counter until lockout (~10 boots = 30 auth failures). Users reported "hours of waiting" on affected hardware (m900). The regression was not immediately visible because the owner passphrase happened to match on some TPM implementations. 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 56efdfd

4 files changed

Lines changed: 54 additions & 29 deletions

File tree

doc/tpm.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,25 @@ 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 — no secret is needed to increment a counter. Previous code
337+
incorrectly passed the TPM owner passphrase as counter auth, which caused
338+
counter operations to fail on TPMs that strictly enforce the spec.
339+
340+
The fix (PR #2117):
341+
- `tpm1_counter_increment` detects an explicit `-pwdc ''` and calls
342+
`tpm counter_increment` directly, bypassing `_tpm_auth_retry`. Non-empty
343+
`-pwdc` or absent flag falls through to the owner-auth retry path for
344+
migration of counters created by pre-fix code.
345+
- `check_tpm_counter` and `increment_tpm_counter` create and increment
346+
counters with `-pwdc ''` instead of the owner passphrase.
347+
- `oem-factory-reset.sh` uses `-pwdc ''` for counter creation.
348+
349+
TPM2 counters are unaffected — they still require owner auth for
350+
`tpm2 nvincrement`.
351+
333352
---
334353

335354
## 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)