fix(sel): open legacy lock keys in binary mode - #6849
Conversation
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Design Review (Fable 5, fork) — ✅ PASSDesign-level review of Both hunks check out against the base: Design-Verdict: PASS Root-cause one-flag fix restoring a documented invariant (key bytes undisturbed by locking), with a deterministic regression pinning it. [DESIGN-REVIEWED] d31e727 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsThe candidate list produced no candidates, and the diff is a purely defensive change: No findings. [OPUS-REVIEWED] d31e727 |
First Principles Review (Fable 5, fork) — ✅ PASSPremise-level review of Everything is verified. The fix targets the only First-Principles-Verdict: PASS Two-line fix at the mechanism level: the open that corrupted the signing key gains the flag ten other call sites already use. What this change shipsIntent: stop Windows from truncating the SEL audit log's HMAC signing key when the chain lock falls back onto the key file itself — a FIX.
The fix sits at mechanism level: the open that mutated the key is the open that changed. Sibling count is zero — [FIRST-PRINCIPLES-REVIEWED] d31e727 |
buluoray
left a comment
There was a problem hiding this comment.
Root-cause, correctly-scoped fix: the SEL chain lock's Windows fallback opened the raw HMAC key file O_RDWR without O_BINARY, and CRT text mode truncated a key ending in 0x1A at open time — mutating the audit-log signing key, not merely flaking a test. Adding getattr(os, "O_BINARY", 0) restores the "locking the key cannot disturb its bytes" invariant, and the deterministic 0x1A-tail fixture pins it instead of leaving a ~1/256 flake.
Verified
sel.py:734is the onlyO_RDWRopen insel.py, so the fix targets exactly the corrupting call site.- The fallback lock target really is
self._hmac_key_file(_chain_lock_path, when the trust subdir is uncreatable), and this fd is never written on that path — the priming write is guarded bylock_path != self._hmac_key_file. Open-time text-mode truncation was therefore the sole corruption vector, whichO_BINARYcloses. - Other
os.opensites are unaffected: the read side already carriesO_BINARY, the dir open isO_DIRECTORY, and the log append is intentional UTF-8 text on a non-key file. - Fixture correctness: with
_HMAC_KEY_MIN_BYTES=32, a 64→63-byte truncation changes the key bytes without tripping the length floor, solegacy.read_bytes() == log._hmac_keyis what reddens on Windows if the flag is removed. - Focused tests pass locally (2 passed). A local mutation on POSIX is inconclusive by design (
O_BINARY == 0there), so the falsification evidence is the deterministic fixture plus the four green Windows shards at this head — noting that explicitly so it does not read as a coverage gap.
Blue
- The log-append handle (
sel.py:1119) wraps its fd in a text-modefdopen("a"), so Windows applies newline translation. Pre-existing and out of scope here, but it is the next place to look if byte-exact cross-platform log output is ever required.
Problem / Motivation
On Windows, the SEL fallback path may use the legacy HMAC key file itself as the byte-range lock target when the protected trust directory cannot be created. That descriptor was opened with
O_RDWRbut withoutO_BINARY.The Windows CRT therefore treats the binary key as a text file. If a valid 64-byte random key ends in DOS EOF byte
0x1A, opening it for update truncates it to 63 bytes before the lock helper receives the descriptor. The existing random fixture hit this with roughly 1/256 probability, so unrelated PRs could fail the cross-process SEL test.Why it matters
This is not only a flaky assertion: the test exposed real mutation of the audit log's signing key on a supported fallback path. Retrying the test would leave both CI attribution and key integrity unreliable.
What changed
getattr(os, "O_BINARY", 0)in addition to the existing flags. It is a no-op off Windows.0x1A, so removingO_BINARYalways fails instead of relying on random chance.No lock timing, retry, timeout, or production write semantics change.
Tests
O_BINARYmakes the deterministic regression fail; restoring it passes consistently.main.test_sel.py: 231 passed, 25 skipped; the only host-specific failure is the existing symlink-privilege capability case owned by test(windows): list seven symlink-creating tests as requiring real symlinks #6752.git diff --checkpassed.No retry, sleep, timeout increase, or random rerun is introduced.
Screenshots / video
Why no screenshot: This changes a Windows file-open flag and backend regression fixture; it has no rendered UI effect.