fix(daemon): fail closed when a peer cannot be pinned - #382
Conversation
# Conflicts: # CHANGELOG.md
…tests lacs-project#382 adds two dispatcher tests (errno classification, and an unpinnable peer keeping only its primary group). The published baseline and the three prose claims move with them. The CHANGELOG conflict came from me populating [Unreleased] after this PR was opened; resolved here, keeping both entries.
vladimirrott
left a comment
There was a problem hiding this comment.
Approving at e4cf823, which carries a merge and a baseline commit of mine; both are explained at the end and neither touches your code.
The insight here is that rc != 0 was being read as one thing when it is two. Only ENOPROTOOPT means "this kernel does not have the option"; EINVAL means "the peer you are asking about is already gone", and the old code answered both with None and then took that as permission to go read /proc/{pid}. Which is the one situation where the PID may already belong to somebody else. Splitting Unsupported from Unpinnable is the whole fix, and it is a two-line idea buried in an errno.
Passing read_groups in rather than calling it is what makes the dangerous branch testable. Forcing real PID reuse in a test is somewhere between flaky and impossible, and you sidestepped it without weakening what is asserted.
I ran it on Linux, since you said plainly that you could not. That statement is worth more than a green checkbox, and it told me exactly where to point the machine.
S0 peer_pin_only_unsupported_option_allows_best_effort_groups ... ok
peer_pidfd_reports_live_self ....................... ok
unpinnable_peer_keeps_only_its_primary_group ....... ok
3 passed
M1 classify every errno as Unsupported (re-open the hole)
-> FAILED, "errno 22"
M2 Unpinnable returns read_groups() instead of dropping them
-> FAILED, "an unpinnable peer must not read a potentially recycled PID"
Both mutations are caught, and M2's message names the actual hazard rather than the assertion. The property that matters holds: a peer that cannot be pinned, whose PID happens to sit in an admin group, resolves to Observer.
I checked the fd < 0 arm too. getsockopt returning 0 with a negative fd is not a case anyone has seen, and treating it as Unpinnable rather than trusting it is the right default for a function whose output decides an authorization level.
Two commits of mine on your branch.
The CHANGELOG conflict was mine to create and mine to fix: I populated [Unreleased] earlier today with entries for four PRs that had merged without them, after you opened this. I merged main and kept both entries, yours leading the Fixed section. Nothing of yours was dropped.
The baseline moved 1,843 to 1,845 for your two tests, across tests/evidence/workspace-tests.json and three prose claims. You were right to leave it alone and say so; CONTRIBUTING documents that fallback and I would rather contributors not have to learn the coupling.
Merging when the board is green. No new issue from me: you have #383 and #384 open with reviews on both, and I released the three I had reserved for you earlier today so you would have room rather than a queue.
|
Narrowing The old code read any The second thing I want to name is the validation section. You wrote that Merged at Both issues behind your open PRs are now assigned to you, not only labelled, so #383 and #384 are the next things in the queue and I have not reviewed either |
A failed
SO_PEERPIDFDlookup previously enabled the old-kernel fallback even when the peer had already been reaped. The daemon now distinguishesPinned,Unsupported(onlyENOPROTOOPT), andUnpinnable; other failures skip the supplementary-group lookup and retain the primary GID captured bySO_PEERCRED.Tests cover errno classification, preservation of the unsupported-kernel path, and primary-group-only caller resolution with an injected unpinnable peer. The existing live-self test now rejects unexpected pin failures. Two Rust tests were added.
Validation on Windows:
cargo fmt --all -- --check,cargo metadata --locked --no-deps --format-version 1,python -X utf8 scripts/check_evidence_claims.py, andgit diff --checkpassed.cargo test -p sysknife-daemon --lib peer_pin_only_unsupported_option_allows_best_effort_groups --lockedcould not compile the unchangedvsockdependency (missing Unix/VSOCK libc and std APIs), so no Rust execution result is claimed locally. Linux kernel 5.15/6.8 live validation is NOT_TESTED locally.Following CONTRIBUTING.md's supported fallback, the four published test-count files are untouched; maintainer regeneration is needed before merge. Codex assisted with this change.
Closes #250.
Linux CI at commit 9eecc1c: 1839 tests run, 1839 passed, 6 skipped. Both added tests and the live-self pidfd test passed. The rust job fails only at the supported-fallback count check (1839 measured versus 1837 recorded); see https://github.com/lacs-project/sysknife/actions/runs/34081372750/job/101617144278. The postgres-contract, docs-and-hygiene, frontend, security-audit and scripts-lint jobs passed.