@@ -12,16 +12,18 @@ use crate::seccomp::notif::{read_child_mem, NotifAction};
1212use crate :: sys:: structs:: { SeccompNotif , ECONNREFUSED } ;
1313
1414use super :: materialize:: {
15- materialize_msg, mmsg_entry_ptr, mmsg_msglen_addr, named_unix_socket_path ,
16- parse_ip_from_sockaddr, parse_port_from_sockaddr, ChildMsghdr , MaterializedMsg ,
17- MAX_SEND_BUF ,
15+ classify_dest_shape , materialize_msg, mmsg_entry_ptr, mmsg_msglen_addr,
16+ named_unix_socket_path , parse_ip_from_sockaddr, parse_port_from_sockaddr, ChildMsghdr ,
17+ MaterializedMsg , MAX_SEND_BUF ,
1818} ;
1919use super :: send_engine:: { batch_send_step, resolve_send, wants_blocking, BatchStep } ;
2020use super :: unix:: {
2121 mmsg_entry_named_unix_path, sendmmsg_named_unix_on_behalf, sendto_named_unix_on_behalf,
22- unix_sendmsg_gate,
22+ sendto_pinned_unix_on_behalf, unix_sendmsg_gate,
23+ } ;
24+ use super :: verdict:: {
25+ check_ip_destination, classify_send_path, path_under_any, DestShape , SendPath ,
2326} ;
24- use super :: verdict:: { check_ip_destination, path_under_any} ;
2527use super :: { query_socket_protocol, socket_is_unix, Protocol } ;
2628
2729// ============================================================
@@ -127,7 +129,95 @@ pub(super) async fn sendto_on_behalf(
127129 )
128130 }
129131 }
130- _ => NotifAction :: Continue ,
132+ _ => {
133+ // Non-IP destination with no fs-path gate: an abstract unix
134+ // address, a named unix address with the fs-gate off, a non-unix
135+ // family (AF_NETLINK/AF_PACKET/AF_VSOCK/...) on any socket. With
136+ // NO destination policy there is nothing to bypass, so Continue
137+ // (matching the connected fast path). Under a destination policy
138+ // do NOT Continue: the kernel would re-read `sockfd` and the
139+ // address after our decision, and a racing
140+ // `dup2(inet_sock, sockfd)` + address swap could ride the Continue
141+ // out to a denied IP. Pin the fd and gate on its STABLE socket
142+ // domain instead. NOTE: this fails closed for non-IP sends on
143+ // non-unix sockets and for AF_UNIX destinations we cannot pin in
144+ // the child's context (abstract / empty / non-UTF-8 `sun_path`)
145+ // under a destination policy — see the PR description.
146+ if !ctx. policy . has_net_destination_policy {
147+ return NotifAction :: Continue ;
148+ }
149+ let dup_fd = match crate :: seccomp:: notif:: dup_fd_from_pid ( notif. pid , sockfd) {
150+ Ok ( fd) => fd,
151+ Err ( e) => return NotifAction :: Errno ( e. raw_os_error ( ) . unwrap_or ( libc:: EBADF ) ) ,
152+ } ;
153+ // The address is non-IP here (parse_ip_from_sockaddr returned
154+ // None), so classify on the pinned socket's stable domain plus the
155+ // destination's ADDRESS FAMILY. Family first: an abstract AF_UNIX
156+ // address and a sockaddr_nl both carry no pathname but are not the
157+ // same case (see DestShape).
158+ let dest = classify_dest_shape ( & addr_bytes) ;
159+ match classify_send_path (
160+ false ,
161+ None ,
162+ socket_is_unix ( dup_fd. as_raw_fd ( ) ) ,
163+ & dest,
164+ ) {
165+ SendPath :: NamedUnixOnBehalf => match & dest {
166+ // Resolve the target in the CHILD's root view and send to
167+ // the pinned inode: the supervisor performs the send, so
168+ // passing the child's raw `sun_path` through would resolve
169+ // it against the supervisor's cwd/root. No fs-grant check
170+ // here — this arm is only reachable with
171+ // `has_unix_fs_gate == false` (the gated case is handled
172+ // above), i.e. the sandbox declares no fs grants to check
173+ // against.
174+ DestShape :: UnixNamed ( path) => sendto_pinned_unix_on_behalf (
175+ notif, notif_fd, dup_fd, buf_ptr, buf_len, flags, path,
176+ ) ,
177+ // Unreachable: the classifier returns NamedUnixOnBehalf
178+ // only for DestShape::UnixNamed. Fail closed rather than
179+ // unwrap — see the panic note below.
180+ _ => NotifAction :: Errno ( libc:: EAFNOSUPPORT ) ,
181+ } ,
182+ // A non-AF_UNIX destination on a unix socket. Send on-behalf
183+ // on the pinned fd with the child's address bytes verbatim:
184+ // this is what sandlock's own NETLINK_ROUTE virtualization
185+ // produces (child fd = one end of a socketpair(AF_UNIX,
186+ // SOCK_SEQPACKET), addressed with a sockaddr_nl), and that
187+ // socket is connected, so the kernel ignores `msg_name`
188+ // outright. Nothing is widened: the fd was pinned before its
189+ // domain was read, so no dup2 can redirect the send, and for
190+ // any other family the kernel rejects the mismatch itself.
191+ SendPath :: RawDestOnBehalf => {
192+ let data =
193+ match read_child_mem ( notif_fd, notif. id , notif. pid , buf_ptr, buf_len) {
194+ Ok ( b) => b,
195+ Err ( _) => return NotifAction :: Errno ( libc:: EIO ) ,
196+ } ;
197+ let m = MaterializedMsg {
198+ data,
199+ control : None ,
200+ addr : addr_bytes,
201+ _scm_fds : Vec :: new ( ) ,
202+ _pinned : None ,
203+ } ;
204+ let blocking = wants_blocking ( dup_fd. as_raw_fd ( ) , flags) ;
205+ resolve_send ( dup_fd, m, flags, blocking)
206+ }
207+ // Reject (a non-IP address on a non-unix socket — the
208+ // address-family-swap shape — or an AF_UNIX address with no
209+ // pathname to pin, notably an ABSTRACT one: the supervisor
210+ // carries no Landlock domain, so sending it on-behalf would
211+ // escape the child's LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET) and,
212+ // defensively, the connected/IP variants that
213+ // classify_send_path(false, None, ..) cannot return: fail
214+ // closed with an errno (parity with the sendmsg Reject arm)
215+ // rather than panic in the seccomp-notif supervisor — a panic
216+ // on this untrusted path would unwind the supervisor task and
217+ // DoS the whole sandbox.
218+ _ => NotifAction :: Errno ( libc:: EAFNOSUPPORT ) ,
219+ }
220+ }
131221 }
132222 }
133223}
0 commit comments