Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions doc/tpm.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,37 @@ counter passes preflight or the user chooses to continue.
exit status of `tee` (always 0), not `tpmr.sh`. See
[ux-patterns.md](ux-patterns.md#tpm-counter-patterns) for the correct pattern.

### TPM1 counter auth

TPM1 rollback counters use **empty auth** (`SHA1("")`) per the TCG
specification (TPM Main Spec Part 3: `TPM_CreateCounter` uses owner
auth `-pwdo`, but `TPM_IncrementCounter` uses the counter's own
authData — not the owner password).

A regression in PR #2068 changed `increment_tpm_counter` from
hardcoded `-pwdc ''` 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`.

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`.

The fix (PR #2117):
- `tpm1_counter_increment`: detect explicit `-pwdc ''` and call
`tpm counter_increment` directly, bypassing `_tpm_auth_retry`.
Non-empty `-pwdc` or absent flag falls through to the owner-auth
retry path for migration of counters created by pre-fix code.
- `check_tpm_counter` and `increment_tpm_counter`: create and
increment counters with `-pwdc ''` instead of owner passphrase.
- `oem-factory-reset.sh`: uses `-pwdc ''` for counter creation.

TPM2 counters are unaffected — they still require owner auth for
`tpm2 nvincrement`.

---

## TPM secret sealing internals (TPM2)
Expand Down
2 changes: 1 addition & 1 deletion initrd/bin/oem-factory-reset.sh
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ generate_checksums() {
if [ "$CONFIG_TPM" = "y" ]; then
if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then
tpmr.sh counter_create \
-pwdc "${TPM_PASS:-}" \
-pwdc '' \
-la -3135106223 |
tee /tmp/counter >/dev/null 2>&1 ||
whiptail_error_die "Unable to create TPM counter"
Expand Down
25 changes: 22 additions & 3 deletions initrd/bin/tpmr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,8 @@ _tpm_auth_retry() {
}

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

# tpm1_counter_increment - Increment a TPM1 rollback counter.
# Args: -ix <index> [ -pwdc <passphrase> ]
#
# Auth behaviour:
# -pwdc '' : empty counter auth (SHA1 of ""), correct per TCG spec.
# Calls tpm directly without retry — caller handles fallback.
# -pwdc <pass> : owner passphrase auth via _tpm_auth_retry (migration).
# (no -pwdc) : owner passphrase auth via _tpm_auth_retry (backward compat).
tpm1_counter_increment() {
TRACE_FUNC
local counter_id=""
local pwdc_provided="n"
local pwdc_value=""
while [ $# -gt 0 ]; do
case "$1" in
-ix) counter_id="$2"; shift 2 ;;
-pwdc) shift 2 ;; # passphrase handled by _tpm_auth_retry
-pwdc) pwdc_provided="y"; pwdc_value="$2"; shift 2 ;;
*) shift ;;
esac
done
_tpm_auth_retry "counter_increment" "stdout" "tpm1" "-pwdc" tpm counter_increment -ix "$counter_id"
if [ "$pwdc_provided" = "y" ] && [ -z "$pwdc_value" ]; then
# Empty counter auth per TCG spec: this is the normal counter
# increment (no secret). Bypass _tpm_auth_retry because the
# empty string is intentional, not a user input error.
# Use || return so set -e doesn't kill the script on DA failure.
DEBUG "tpm1_counter_increment: empty auth, calling tpm directly"
tpm counter_increment -ix "$counter_id" -pwdc '' || return $?
else
_tpm_auth_retry "counter_increment" "stdout" "tpm1" "-pwdc" \
tpm counter_increment -ix "$counter_id"
fi
}

tpm2_counter_create() {
Expand Down
37 changes: 12 additions & 25 deletions initrd/etc/functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,8 @@ check_tpm_counter() {
TRACE_FUNC

LABEL=${2:-3135106223}
tpm_passphrase="$3"
# $3 (tpm_passphrase) was used by pre-PR #2068 code but is now intentionally
# ignored — counters are created with empty auth (-pwdc '') per TCG spec.
# if the /boot.hashes file already exists, read the TPM counter ID
# from it.
if [ -r "$1" ]; then
Expand All @@ -1857,12 +1858,8 @@ check_tpm_counter() {
DEBUG "Extracted TPM_COUNTER: '$TPM_COUNTER' from $1"
else
DEBUG "$1 does not exist - creating new TPM counter"
# Warn user: TPM Owner Passphrase is required to create a new TPM counter
if [ ! -s /tmp/secret/tpm_owner_passphrase ]; then
WARN "TPM Owner Passphrase is required to create a new TPM counter for /boot content rollback prevention"
fi

# attempt to make a new counter, capturing any stderr for debugging
# Create TPM counter with empty counter auth per TCG spec (no secret).
# Owner passphrase is not needed for the counter auth itself.
DEBUG "Invoking tpmr.sh counter_create with label $LABEL"
# run it, then record the exit status explicitly; the '!' operator
# cannot be used because it would hide the real return code.
Expand All @@ -1872,7 +1869,7 @@ check_tpm_counter() {
(
set +e
tpmr.sh counter_create \
-pwdc "${tpm_passphrase:-}" \
-pwdc '' \
-la "$LABEL" \
>/tmp/counter 2> >(tee >(SINK_LOG "tpm counter_create stderr") >&2)
echo $? > /tmp/counter_create_rc
Expand Down Expand Up @@ -2050,22 +2047,11 @@ increment_tpm_counter() {
counter_present="y"
fi

# Prefer explicit passphrase, otherwise reuse cached TPM owner passphrase.
# TPM2 uses owner-auth fallback in tpm2_counter_inc; TPM1 uses empty counter
# auth (SHA1("")) per TCG spec — no owner passphrase needed for increment.
# Keep the cached owner passphrase for TPM2 fallback.
if [ -z "$tpm_passphrase" ] && [ -s /tmp/secret/tpm_owner_passphrase ]; then
tpm_passphrase="$(cat /tmp/secret/tpm_owner_passphrase)"
DEBUG "increment_tpm_counter: using cached TPM owner passphrase"
fi

# TPM1 counter_increment requires owner auth in practice on this path.
# origin/master typically reached this with cached owner passphrase already set,
# but the newer reseal/update flows can call this later in the session after
# that cache is absent. Prompt once and cache to avoid empty -pwdc failures.
if [ "$CONFIG_TPM2_TOOLS" != "y" ] && [ -z "$tpm_passphrase" ]; then
WARN "TPM Owner Passphrase is required to update rollback counter before signing updated boot hashes."
DEBUG "increment_tpm_counter: TPM1 path has no cached/provided owner passphrase; prompting now"
prompt_tpm_owner_password
tpm_passphrase="$tpm_owner_passphrase"
DEBUG "increment_tpm_counter: TPM1 owner passphrase obtained and cached"
fi

# Try to increment the counter. We normally hide the verbose
Expand Down Expand Up @@ -2094,7 +2080,7 @@ increment_tpm_counter() {
increment_ok="y"
fi
else
# TPM1 path uses owner auth in practice.
# TPM1 counter uses empty auth (SHA1 of "") per TCG spec.
# NOTE: tpmtotp C code prints ALL output (success + errors) to stdout.
# We must capture stdout to detect failures properly.
# DO_WITH_DEBUG internally captures the command's stderr (tee /dev/stderr
Expand All @@ -2104,7 +2090,7 @@ increment_tpm_counter() {
if (
set -o pipefail
DO_WITH_DEBUG --mask-position 5 \
tpmr.sh counter_increment -ix "$counter_id" -pwdc "${tpm_passphrase:-}" \
tpmr.sh counter_increment -ix "$counter_id" -pwdc '' \
2>/dev/null | tee /tmp/counter-"$counter_id" >/dev/null
Comment thread
tlaurion marked this conversation as resolved.
); then
increment_ok="y"
Expand All @@ -2123,10 +2109,11 @@ increment_tpm_counter() {

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