Skip to content

init/luks: dispatch PIN-bearing tokens serially in token-ID order#353

Merged
anatol merged 2 commits into
anatol:masterfrom
pilotstew:pr/pintoken-scheduler
May 5, 2026
Merged

init/luks: dispatch PIN-bearing tokens serially in token-ID order#353
anatol merged 2 commits into
anatol:masterfrom
pilotstew:pr/pintoken-scheduler

Conversation

@pilotstew
Copy link
Copy Markdown
Contributor

Two PIN-bearing tokens (e.g. TPM2-PIN and FIDO2-PIN) enrolled to the same volume are already serialized by inputMutex (and fido2Mu), so prompts queue rather than overlap. The remaining issue is ordering — parallel goroutines race to grab the mutex, so the user sees prompts in non-deterministic order each boot. With more than one PIN method this is confusing: a user who enrolled TPM2 first as their preferred unlock can't tell which boot will actually prompt for it first.

Change

Classify tokens up front in luksOpen:

  • PIN-bearing tokens (TPM2 with tpm2-pin, FIDO2 with fido2-clientPin-required) collect into a pinTokens slice.
  • Non-PIN tokens fan out as today.

A single goroutine then walks pinTokens in slice order — already sorted by ID via the sort.Slice in luksOpen — calling recoverTokenPassword serially. A skipped/failed token advances to the next; a successful unlock closes done and stops the loop.

The done check before each PIN iteration also lets a parallel non-PIN unlock cancel the loop without waiting on the next prompt to time out.

New predicate

tokenNeedsPin(t luks.Token) bool parses the token payload JSON to determine dispatch group. Returns false for malformed payloads, missing fields, and unrelated token types so behaviour falls through to today's parallel path on any classification doubt.

Tests

init/token_orchestration_test.go:

  • TestTokenNeedsPin — table-driven JSON classification across both types
  • PIN-loop ordering, short-circuit on success, parallelism with non-PIN, cancellation on non-PIN success, skip-advances-loop, sort determinism
  • A negative-form contract test that documents runPinTokens does NOT sort — sorting is luksOpen's responsibility

The production loop is mirrored by runPinTokens so it can be exercised without real LUKS devices, swtpm, or hardware tokens.

pilotstew added 2 commits May 4, 2026 19:06
Two PIN-bearing tokens (e.g. TPM2-PIN and FIDO2-PIN) enrolled to the
same volume are already serialized by inputMutex (and fido2Mu), so
prompts queue rather than overlap. The remaining issue is ordering —
parallel goroutines race to grab the mutex, so the user sees prompts
in non-deterministic order each boot. With more than one PIN method
this is confusing: a user who enrolled TPM2 first as their preferred
unlock can't tell which boot will actually prompt for it first.

Classify tokens up front: PIN-bearing tokens (TPM2 with tpm2-pin or
FIDO2 with fido2-clientPin-required) collect into a pinTokens slice;
everything else fans out as today. A single goroutine then walks
pinTokens in slice order — already sorted by ID via the sort.Slice
in luksOpen — calling recoverTokenPassword serially. A skipped token
advances to the next; a successful unlock closes done and stops the
loop. Non-PIN tokens are unaffected and still run in parallel.

The done check before each PIN iteration also lets a parallel non-PIN
unlock cancel the loop without waiting on the next prompt to time out.
Cover tokenNeedsPin's JSON classification (TPM2 with/without tpm2-pin,
FIDO2 with/without fido2-clientPin-required, malformed payloads,
unrelated token types) and the PIN-token serial loop semantics:

- ordering: PIN tokens run in slice order, fully completing each
  before starting the next
- short-circuit: a successful PIN unlock stops trailing PIN tokens
- parallelism: non-PIN tokens fan out independently of the PIN loop
- cancellation: a non-PIN success closes done and the PIN loop exits
  without dispatching the next prompt
- skip-advances-loop: returns(false) (the errTPM2Skipped /
  errFido2FallbackToKeyboard path) lets the next PIN token run
- sort determinism: shuffled input sorted by ID dispatches in
  ascending ID order

The production loop is mirrored by runPinTokens so it can be exercised
without real LUKS devices, swtpm, or hardware tokens. init and
generator tests pass.
@anatol anatol merged commit ec068aa into anatol:master May 5, 2026
@anatol
Copy link
Copy Markdown
Owner

anatol commented May 5, 2026

Thank you!

@pilotstew pilotstew deleted the pr/pintoken-scheduler branch May 9, 2026 02:53
pilotstew added a commit to pilotstew/booster that referenced this pull request May 14, 2026
Adds a new NOTES subsection covering the concurrent-unlock model that
landed across PRs anatol#350, anatol#353, anatol#355, anatol#356, anatol#357, anatol#358, and anatol#362:
PIN-token serialization in ascending LUKS2 token-ID order, cancel-on-win
semantics for keyboard/FIDO2-PIN/TPM2-PIN prompts on both the console
and the Plymouth splash (with the MR !393 caveat for older Plymouth
builds), and the per-token 3-attempt PIN cap with empty-PIN skip.

Trims two paragraphs from the existing 'Password entry' subsection
(auto-dismiss and PIN attempts) now that the new section covers them
in fuller context. 'Password entry' keeps the Ctrl+W / Ctrl+U / Tab
edit-key reference.
anatol pushed a commit that referenced this pull request May 14, 2026
Adds a new NOTES subsection covering the concurrent-unlock model that
landed across PRs #350, #353, #355, #356, #357, #358, and #362:
PIN-token serialization in ascending LUKS2 token-ID order, cancel-on-win
semantics for keyboard/FIDO2-PIN/TPM2-PIN prompts on both the console
and the Plymouth splash (with the MR !393 caveat for older Plymouth
builds), and the per-token 3-attempt PIN cap with empty-PIN skip.

Trims two paragraphs from the existing 'Password entry' subsection
(auto-dismiss and PIN attempts) now that the new section covers them
in fuller context. 'Password entry' keeps the Ctrl+W / Ctrl+U / Tab
edit-key reference.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants