Skip to content

fix(privops): fail closed when getpeereid fails on the libnv socket (1.1.19, security)#221

Merged
click0 merged 1 commit into
mainfrom
claude/analyze-test-coverage-nCOJW
Jul 5, 2026
Merged

fix(privops): fail closed when getpeereid fails on the libnv socket (1.1.19, security)#221
click0 merged 1 commit into
mainfrom
claude/analyze-test-coverage-nCOJW

Conversation

@click0

@click0 click0 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

daemon/privops_listener.cpp left peerUid = 0 on a getpeereid failure and proceeded to dispatch. With rootless_per_user enabled the authorize-before-dispatch gate is if (rootlessPerUser && operatorUid > 0), so peerUid = 0 was treated as the admin/host-wide path and every per-tenant gate was skipped — a fail-open on identity loss (peer exiting between accept and getpeereid, or a kernel edge). The old comment even asserted "getpeereid only fails on non-AF_UNIX sockets which we don't accept" — not guaranteed.

Fix

The listener now rejects with 403 when getpeereid fails and rootless_per_user is on, instead of running the verb unauthenticated.

  • rootless_per_user off → nothing to gate (peerUid only fed the audit trail) → harmless no-op, connection proceeds unchanged.
  • Root operator whose getpeereid succeeds with uid 0 → unaffected; that's still the intended admin treatment. Only a genuine getpeereid failure is rejected.
  • errno captured before applyConnectionRights (Capsicum) can clobber it, for the diagnostic log.

Closes the "fail closed on identity loss" guardrail (item 3) in docs/trust-model.md.

Verification

⚠️ This is daemon-side (crated), FreeBSD-only, and not exercised by automated CI — the lite workflow links only crate(1), not crated, and the full FreeBSD workflow is manual (workflow_dispatch). The change is small and mechanical (add a bool, capture errno, add an early-return guard); includes verified (<cerrno>/<cstring> present), writeErrorResponse uses nvlist_send which is permitted after the Capsicum rights-limit, and the logic was reviewed against the dispatch gate.

  • Includes + writeErrorResponse signature verified.
  • Logic reviewed: fail-closed only on genuine getpeereid failure under rootless; root-uid-0-success and non-rootless paths unchanged.
  • gmake crated compile — needs the manual full FreeBSD build (structural CI gap: crated isn't in lite).
  • On-hardware: confirm a rootless privops connection whose peer exits mid-handshake is rejected 403, and normal connections are unaffected.

Docs

docs/trust-model.{md,uk.md}: guardrail item 3 marked Done (1.1.19), Applies-to → 1.1.19. CHANGELOG [1.1.19] + --version bump.

Note on the structural CI gap

That a daemon-only change like this has no automated compile coverage is exactly the crated/crate-snmpd-not-linked-in-CI gap flagged in the earlier project bug analysis. Worth a follow-up (stub-link the daemon binaries in lite) so daemon changes get at least a compile gate.

https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK


Generated by Claude Code

…1.1.19, security)

daemon/privops_listener.cpp left peerUid = 0 on a getpeereid failure
and proceeded to dispatch. With rootless_per_user enabled the
authorize-before-dispatch gate is `if (rootlessPerUser && operatorUid
> 0)`, so peerUid = 0 was treated as the admin/host-wide path and EVERY
per-tenant gate was skipped — a fail-open on identity loss (e.g. the
peer exiting between accept and getpeereid, or a kernel edge). The old
comment even asserted "getpeereid only fails on non-AF_UNIX sockets
which we don't accept", which is not guaranteed.

The listener now rejects the connection with 403 when getpeereid fails
AND rootless_per_user is on, instead of running the verb
unauthenticated. When per-user enforcement is off there is nothing to
gate (peerUid only fed the audit trail), so a failure there stays a
harmless no-op and the connection proceeds unchanged. A root operator
whose getpeereid SUCCEEDS with uid 0 is unaffected — that remains the
intended admin treatment; only a genuine getpeereid FAILURE is
rejected. errno is captured before applyConnectionRights (Capsicum)
can clobber it, for the diagnostic log.

Closes the "fail closed on identity loss" guardrail in
docs/trust-model.md (item 3). Note: this is daemon-side (crated),
FreeBSD-only, and not exercised by the automated lite CI (which links
only crate(1)); verified by careful review + the manual full FreeBSD
build. docs/trust-model.{md,uk.md} mark the guardrail done;
CHANGELOG [1.1.19] + --version bump.

https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
@click0 click0 merged commit 3497ac7 into main Jul 5, 2026
2 checks passed
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