[draft] fix(net): symmetric named-AF_UNIX datagram gating (reference implementation)#130
[draft] fix(net): symmetric named-AF_UNIX datagram gating (reference implementation)#130dzerik wants to merge 1 commit into
Conversation
|
I think the sendto part is a reasonable fix, the sendmsg/sendmmsg part may regress from fail-closed to a TOCTOU-exploitable Continue. |
A named/abstract AF_UNIX SOCK_DGRAM sendmsg/sendmmsg under a destination policy (with the unix fs-gate off) failed closed with EAFNOSUPPORT, while the identical sendto Continue'd and worked -- the two syscalls disagreed on the same destination. Rather than resolve this by Continuing sendmsg (which would decide on the transient address family and let a racing dup2(inet_sock, sockfd)+msg_name swap ride out on the kernel's re-read to a denied IP), gate on the stable socket domain and send the unix datagram ON-BEHALF on the pinned fd: - Add a pure `classify_send_path(connected, ip, is_unix_socket)` verdict. A non-IP address on a unix-domain socket -> UnixOnBehalf (send, no IP check); on a non-unix socket -> Reject (EAFNOSUPPORT, the addr-family swap shape). - send_msghdr_on_behalf (sendmsg/sendmmsg) uses it: a unix datagram is materialized and sent on the pinned dup_fd, never Continue. - sendto's non-IP branch matches: no destination policy -> Continue (nothing to bypass); policy active -> send a unix datagram on-behalf, fail closed on a non-unix socket. All three send paths now behave identically for unix datagrams and never Continue under a destination policy, closing the re-read TOCTOU window. The decision is unit-tested in verdict.rs (four SendPath arms); the handler wiring is exercised by the kernel integration suite.
f8e3ea9 to
a4f6d97
Compare
|
You're right — the sendmsg/sendmmsg Continue was TOCTOU-exploitable, and gating on the socket domain doesn't fix it: Reworked (rebased onto current main after the #128 split; force-pushed). Instead of Continue, a named/abstract unix datagram is now sent on-behalf on the pinned
The decision is a pure Still a draft — priority and the in-sandbox path are your call. (Related: the chroot named-AF_UNIX gate has the same Continue-on-allow shape on its lexical fast path; I filed that separately as #143 so this PR stays scoped to the datagram symmetry.) |
|
The sendto tightening is a high-priority fix, in my opinion. Maybe you want to separate it out as a fix which could land quickly. |
Draft / reference implementation — not requesting merge. Posted so the concrete shape of the fix is reviewable; you own the OCI in-sandbox path, so priority and testing are your call.
Finding
A named (pathname/abstract)
AF_UNIXSOCK_DGRAMsendmsg/sendmmsgunder a destination policy with the unix fs-gate off returnsEAFNOSUPPORT, while the identicalsendtoContinues and works. The IP send path (send_msghdr_on_behalf→parse_ip_from_sockaddr→None) fails closed for a non-IP address, butsendto's non-IP branch (_ => Continue) does not — so the two syscalls disagree on the same destination.Separately,
sendto's_ => Continuedecides on the transient address family: anAF_INETsocket presenting anAF_UNIXaddress at check timeContinues, and a racing thread can then swapmsg_nameto a denied IP before the kernel re-reads it — a latent TOCTOU on the destination policy.Fix
Gate on the stable socket domain (
socket_is_unix,SO_DOMAIN), not the address family:send_msghdr_on_behalfreturns aMsgOutcome: a non-connected, non-IP addressContinues when the socket isAF_UNIX(the kernel constrains anAF_UNIXsocket toAF_UNIXdestinations, so a swap to an IP is kernel-rejected — the re-read race can't bypass the IP policy), and fails closed withEAFNOSUPPORTon anAF_INETsocket.sendmsg/sendmmsgreturnContinueon that outcome (in a batch only at entry 0, since a unix socket carries only unix entries).sendto's_ => Continueis tightened the same way, closing the TOCTOU above.AF_UNIX(the fix(net): pass through connected AF_UNIX sendmsg under a destination policy #125 path) is unchanged — still on-behalf.Reachability — why this is low-severity and hard to test
The precondition is the unix fs-gate being off, which means empty fs grants, which means Landlock denies all filesystem access. That makes the path unreachable through every exec-based run (
run/spawn):execveneeds a Landlock EXECUTE grant, and any grant turns the fs-gate on.fork()/work_fninstalls a kernel-only seccomp filter with no user-notification supervisor, so the on-behalf handler never runs there either.The only path that reaches
sendmsg_on_behalfunder an off fs-gate is the OCI in-sandbox PID-1 (ChildEntry::InProcess): a no-execchild with the full notif supervisor. So this affects a self-confining process (empty fs, net policy, named-unix datagram) — real, but niche, hence no destination-policy bypass and only an availability/consistency wart. A dedicated end-to-end test needs the OCI in-process harness (the python integration harness can't run under an off fs-gate), which is why this is a draft rather than a merge-ready PR with its own regression test.Full
sandlock-corelib suite is green (444) with no regressions;cargo clippyadds no new warnings. Happy to fold this in (or drop it) per your read on priority.