Repository navigation
Sandbox DNS relay requires ip_unprivileged_port_start=0: options for platforms where the driver can't set it #5095
Replies: 2 comments
|
Correction to (iii), from a measurement after posting: I excluded the nested user+net namespace shape because (The earlier EINVAL was a measurement artifact: So (iii) belongs on the list as a real option where unprivileged user namespaces are allowed: the broker stays in the outer namespace while the workload's resolver and relay live on the nested loopback. It doesn't help where user namespaces are blocked, which is where (i) or (ii) still matter. The question stands as posted: which shape, if any, would you accept? |
|
An update with a working prototype, in case it helps decide between the shapes. It implements shape (ii) and is opt-in: when the variable below is unset, behaviour and the qualification report are unchanged. Branch: What it does:
Tests:
End to end, on a stock Linux host with
If one of these shapes, or a different mechanism, would be acceptable, I'm happy to turn it into an issue and follow the vouch and PR process. |
Uh oh!
There was an error while loading. Please reload this page.
Problem
openshell-sandboxrequiresnet.ipv4.ip_unprivileged_port_start = 0in the workload's network namespace and refuses to start otherwise (crates/openshell-sandbox/src/main.rs:226-238at3a5dfcd), because the network broker binds its DNS relay on127.0.0.53:53as an unprivileged process (network_broker.rs:36,DNS_RELAY_ADDRESS).Every shipped compute driver meets this by setting the sysctl itself: Docker and Podman via container sysctls, Kubernetes via the pod
sysctls(and the driver validates it), the VM driver from its init script. An external compute driver can only meet it if its platform lets the driver set per-container sysctls or grantCAP_NET_BIND_SERVICE. On managed container platforms that expose neither (/proc/sysread-only, no capabilities, user namespaces blocked), the sandbox can't start even when the kernel has everything else: Landlock ABI ≥ 3, seccomp user-notify withADDFD|SEND, loopback, and so on.A port number on loopback carries no security property here: the relay stays loopback-only and is still reached only through the broker's mediation. So this question is about how the broker reaches its relay, not about relaxing anything.
What the broker already does
connect()to the relay is intercepted.destination == dns_relay.addressis matched, thenconnect_exact(source_fd, destination)pins the socket (network_broker.rs:945-962).bind_dns_relay_socketsalready supports port 0, with a TCP/UDP pair-retry loop (:509; used by tests viastart_for_test).DNS_RELAY_ADDRESSserves as both the address the agent's resolver targets (drivers writenameserver 127.0.0.53, and libc resolvers use port 53) and the address the relay binds.Splitting those two, advertised
127.0.0.53:53(what the broker matches) and bound127.0.0.53:N(what it connects to), looks like the natural change. It isn't enough on its own, because resolvers differ:resolv/res_send.c,send_dg)connect()s the UDP socket to the nameserver and sends without an address. No source check afterrecvfrom; it relies on the kernel's connected-socket filterconnect()rewrite pins to:N, and replies from:Npass the kernel filternet), any TCP fallbackconnect()then stream I/Osrc/network/res_msend.c), e.g. statically linked clients such asuv(the broker comment at:1444calls this case out)sendto(…, ns[j])with the port hard-coded to 53, and a reply is accepted only ifmemcmp(ns+j, &sa, sl) == 0: family, port and addressrespond_continue(:1478,:1490), so the kernel delivers the datagram to the explicit:53, where nothing listens. (2) Replies from:Nfail musl's source comparison and are dropped.Three shapes. Which, if any, would you accept?
(i) Emulate DNS UDP for sockets whose destination is the advertised address. In fallback mode the broker performs the send itself: it copies the payload (
process_vm_readv), sends from the retained fd to:N, and returns the byte count instead ofrespond_continue. It also interceptsrecvmsg/recvfromon sockets inDnsUdpstate, writing the reply back withmsg_name = 127.0.0.53:53. This is correct for every resolver. It's the largest change innetwork_broker.rs, and the receive interception means addingrecvmsg/recvfromto the notify filter, which BPF can't narrow by socket type. So it would be installed only when the relay isn't on:53.(ii) Explicit operator opt-in that fails loudly instead of silently. For example, a sandbox setting (driver-supplied) that binds the relay on an unprivileged port when
:53isn't bindable:With the relay off
:53:connect()paths (glibc, Go, TCP) are rewritten as above.sendto/sendmsgwhose destination is the advertised:53gets an immediate error from the broker (e.g.ECONNREFUSED) with an OCSF denial ("DNS relay on unprivileged port; unconnected UDP resolvers unsupported in this mode"), instead ofrespond_continue.glibc, Go and TCP work; musl resolvers fail at the first query with a clear reason in the audit log. Nothing changes when
:53is bindable, which covers every shipped driver.(iii) (considered, not proposed) Nested user+net namespace for the workload, with the relay bound on that namespace's own loopback, where the namespace owner may bind 53. This would also add isolation, but the platforms that motivate this are exactly the ones that block unprivileged user namespaces (
unshare(CLONE_NEWUSER)→ EPERM in the container), so it doesn't help there.What I'm asking
Is relaxing the
ip_unprivileged_port_start = 0precondition something you'd take at all? If so, which shape: (i), (ii), or another you'd prefer? I can write the change once there's a preferred direction (and after a vouch request). I'm not proposing it as a default change for the shipped drivers.Acceptance criteria (for whichever shape)
127.0.0.53:53is bindable.main.rsupdated to match.Investigation done with an agent against
3a5dfcd; the code references above were checked by reading the source.All reactions