init/console: replace cooked-mode reader with raw-mode CSI scanner#355
Merged
Conversation
Replaces console.go's readPasswordLine / readPasswordLocked / readPassword (cooked-termios bufio over stdin) with a new console_input.go raw-mode reader that honors ctx cancellation. Headline behaviour fix: when a non-interactive token (TPM2 PCR-only, touchless FIDO2, or clevis) wins the unlock race while the keyboard prompt is showing, the cooked reader stays blocked in read(2) — the prompt dangles, inputMutex/keyboardMu stay held, and any subsequent volumes that need keyboard entry block indefinitely until the user manually presses Enter on the now-pointless prompt. After this change the reader checks ctx.Done() between Poll(100ms) cycles, returns ctx.Err() within ~100ms of cancellation, restores termios via defer, ends the prompt line cleanly, and releases the locks. Scope is the keyboard-passphrase path only: - askPasswordWithFallback / readPassword / readPasswordLocked take ctx context.Context (threaded from requestKeyboardPassword, which is already ctx-aware after the prior PR in this series). - FIDO2-PIN and TPM2-PIN call sites pass context.Background() and continue to dangle on autounlock; a follow-up PR threads ctx into recoverFido2Password / recoverSystemdFido2Password / recoverSystemdTPM2Password to close that gap. Scanner design: - Per-byte FSM consuming ANSI CSI / SS3 / OSC / DCS sequences and bracketed-paste markers so they never enter the password buffer. - UTF-8 multi-byte reassembly across read boundaries with overlong / surrogate / >U+10FFFF rejection. - Editing keys: Backspace, Delete, Ctrl+W (word kill), Ctrl+U (line kill), Tab (mask toggle), Enter (CR/LF/Ctrl+D). - Inspired by Plymouth's on_key_event in src/libply-splash-core/ply-keyboard.c with intentional divergences (kept ISIG so Ctrl+C still cancels; accept BS and DEL; proper word-kill; OSC/DCS/bracketed-paste handling; bounded CSI parameter accumulation; UTF-8 validation). UTF-8 helpers are direct ports of ply_utf8_*; the scanner itself is reimplemented as a per-byte FSM rather than a slice-scan. Bracketed-paste support has a specific forward-compat value for the in-flight remote SSH unlock work (PR anatol#320): SSH users typically paste passphrases from password managers, and modern terminal emulators bracket the pasted content with \x1b[200~ ... \x1b[201~ markers. A cooked-mode reader would treat those markers as literal password bytes and corrupt the entry. K's scanner consumes the markers and preserves the pasted content verbatim. Termios: ECHO/ICANON/IEXTEN/IXON/IXOFF/BRKINT/INPCK/ISTRIP/INLCR/ IGNCR cleared in raw mode; ISIG kept on. VMIN=1, VTIME=0. Every return path restores the original termios via defer. Notably IXON off means Ctrl+S no longer freezes the prompt. No new dependency: the scanner is ~440 LOC of stdlib Go. Surveyed alternatives (golang.org/x/term, charmbracelet/x/input, peterh/liner, eiannone/keyboard, mattn/go-tty) either don't support cancellable raw reads or pull substantial transitive deps inappropriate for an initramfs.
Two test files covering the new raw-mode reader. console_input_test.go (~400 LOC, 22+ tests) exercises the scanner state machine directly via Feed(), with no terminal involved: - UTF-8 split across Feed boundaries; 2/3/4-byte codepoints; mixed ASCII; overlong / surrogate / out-of-range rejection - CSI / SS3 / function-key / arrow-key sequences silently consumed - OSC body terminated by BEL or ST does not leak into the password - DCS body terminated by ST does not leak - Bracketed-paste markers consumed; embedded control bytes (newline, Ctrl+U, etc.) pass through as literal bytes - CSI parameter accumulator is bounded (16 bytes) — overflow aborts the sequence and recovers without lock-up - Editing keys: Backspace, Delete, Tab, Ctrl+U, Ctrl+W, Enter (CR / LF / Ctrl+D) console_input_pty_test.go (~190 LOC, 3 tests) exercises the end-to-end ctx-cancel behaviour via a real Linux pty pair (/dev/ptmx + TIOCSPTLCK + TIOCGPTN), so termios setup, the Poll(100ms) loop, and the cancellation path are all verified with a real terminal: - TestReadPasswordOnCtxCancelReturnsPromptly: the headline test. ctx.Done() fires while the reader is blocked in Poll; the reader returns ctx.Err() within ~100ms; termios is restored to its pre-call state (Lflag and Iflag both checked). - TestReadPasswordOnReadsTypedBytes: bytes typed via the pty master end are received and assembled into the password returned on Enter. - TestReadPasswordOnCancelAfterPartialInput: cancellation after partial input discards the partial buffer and returns ctx.Err() — the partial bytes are NOT returned as a password.
New "Password entry" section under NOTES covers the user-visible behaviour this PR introduces — editing keys (Ctrl+W word-kill, Ctrl+U line-kill, Tab mask-toggle) and auto-dismiss when any unlock method wins the race — and backfills documentation for the previously- shipped PIN-prompt behaviours from anatol#347 and anatol#351 (3 attempts; empty PIN to skip the token to the next unlock prompt).
Owner
|
Fantastic! Thank you very much! |
anatol
pushed a commit
that referenced
this pull request
May 6, 2026
Threads ctx context.Context into recoverFido2Password, recoverSystemdFido2Password, and recoverSystemdTPM2Password. The PIN prompts in those functions previously called askPasswordWithFallback with context.Background() and could not be cancelled when a sibling unlock succeeded. With ctx propagated, every console password prompt — keyboard-passphrase, FIDO2-PIN, TPM2-PIN — now dismisses cleanly. recoverTokenPassword (already ctx-aware after #354) updates its two systemd-token call sites to pass ctx through. Pure additive — no behavioural change beyond extending the cancellation reach. Also drops three stale comments left in #355 that referenced this upcoming work in internal-planning vocabulary; reworded to factually describe the current state.
anatol
pushed a commit
that referenced
this pull request
May 6, 2026
Two changes that together close out the console UX for booster's
concurrent unlock pipeline:
1. Prompt-aware statusMessage redraw. statusMessage now consults the
active prompt before printing to console. If a passphrase prompt
is on screen and its volume hasn't been unlocked yet, the current
line is erased, the message prints, and the prompt is reprinted
below — cursor stays at the bottom, asterisk count preserved.
The new promptVolumeUnlocked helper lets statusMessage skip the
redraw when the prompt's volume is already unlocked, avoiding
reprinting a stale prompt that ctx-cancel hasn't yet torn down.
readPasswordOn now sets consolePrompt.{active,text,done} during
each prompt — fields declared in #355 that become load-bearing
only now that statusMessage consumes them.
2. Token-unlock confirmation. recoverTokenPassword fires
statusMessageTimed("X unlocked via Y", 3s) on success. After
#355 and #356 cleanly dismiss prompts when a sibling token wins
the race, the user previously saw nothing telling them what
happened — boot just continued. This adds the missing
confirmation. tokenFriendlyName provides the short label per
token type; statusMessageTimed clears the message after 3s so
it doesn't linger.
5 tasks
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Replaces
console.go's cooked-termios reader (readPasswordLine+readPasswordLocked+readPassword) with a newconsole_input.goraw-mode reader that honors ctx cancellation. Builds on #354 which establishedcontext.Contextas the cancellation idiom in unlock paths.Three commits: implementation, scanner + pty-harness tests, manpage section.
console_input.gois intentionally over-commented (state-machine flow diagram, per-flag termios rationale, acronym key block) to keep the dense FSM code review-tractable.Headline behaviour fix
When a non-interactive token (TPM2 PCR-only, touchless FIDO2, or clevis) wins the unlock race while the keyboard prompt is showing, the cooked-mode reader has no way to honor the cancellation: it stays blocked in
read(2). The prompt dangles,inputMutex/keyboardMustay held, and any subsequent volumes that need keyboard entry block until the user manually presses Enter on the now-pointless prompt.After this change the reader checks
ctx.Done()betweenPoll(100ms)cycles, returnsctx.Err()within ~100 ms of cancellation, restores termios via defer, ends the prompt line cleanly, and releases the locks. End-to-end behaviour: when a token wins, the keyboard prompt dismisses on its own.Bonuses that fall out of raw mode
\x1b[200~ ... \x1b[201~markers. The cooked-mode reader treated those as literal password bytes and corrupted entries from password managers. The new scanner consumes the markers and preserves the pasted content verbatim. Particularly relevant to the in-flight remote SSH unlock work in Add remote ssh LUKS device unlocking #320.Scope and acknowledged gap
Keyboard-passphrase path only.
askPasswordWithFallback/readPassword/readPasswordLockedtakectx context.Contextand the call site inrequestKeyboardPasswordthreads it through (already ctx-aware after #354).FIDO2-PIN and TPM2-PIN call sites pass
context.Background()and continue to dangle on autounlock — closing this gap belongs to an upcoming PR that threads ctx intorecoverFido2Password/recoverSystemdFido2Password/recoverSystemdTPM2Password. Honest about the gap so the staged review works.Scanner design
Per-byte FSM consuming ANSI CSI / SS3 / OSC / DCS sequences and bracketed-paste markers so they never enter the password buffer. UTF-8 multi-byte reassembly across read boundaries with overlong / surrogate / >U+10FFFF rejection (via
utf8.Valid). Bounded CSI parameter accumulation (16 bytes) — garbage input can't lock up the scanner.Inspired by Plymouth's
on_key_eventinsrc/libply-splash-core/ply-keyboard.c, with intentional divergences:cfmakerawclears it).\x08) and DEL (\x7f) as backspace; Plymouth only DEL.UTF-8 helpers (
trimLastCodepoint, byte-pattern table) are direct ports of Plymouth'sply_utf8_*functions; the CSI scanner itself is reimplemented as a per-byte FSM rather than Plymouth's slice-scan.No new dependency
The scanner is stdlib Go only. Surveyed alternatives (
golang.org/x/term,charmbracelet/x/input,peterh/liner,eiannone/keyboard,mattn/go-tty) either don't support cancellable raw reads (the bug we're fixing) or pull substantial transitive deps inappropriate for an initramfs.Termios contract
Raw mode clears: ECHO, ICANON, IEXTEN, IXON, IXOFF, BRKINT, INPCK, ISTRIP, INLCR, IGNCR. Keeps ISIG on. VMIN=1, VTIME=0. Every return path restores the original termios via defer.
No signal-based safety net (sigaction). A process killed mid-read could leave the terminal in raw mode until systemd takes over — acceptable for booster's PID-1 use case (rarely takes signals during boot).
Testing
Two test files. Scanner unit tests in
console_input_test.goexercise the state machine directly viaFeed()(CSI / SS3 / OSC / DCS / bracketed-paste / UTF-8 split across reads / editing keys / killWord boundary semantics).Pty-harness end-to-end tests in
console_input_pty_test.goexercise ctx cancellation against a real Linux pty pair (/dev/ptmx):Live-tested on local tty1: passphrase entry, FIDO2-touchless-wins-while-prompting (the headline fix verified), wrong passphrase retry, Tab masking toggle. Multi-volume and serial console behavior matches in design but not separately tested.
Risks
Manpage
Adds a "Password entry" section under NOTES documenting editing keys, asterisk echo, auto-dismiss on autounlock-win, and language support.