Skip to content

fix(net): cap child-controlled sockaddr length before reading it#129

Open
dzerik wants to merge 2 commits into
multikernel:mainfrom
dzerik:fix/net-sockaddr-len-cap
Open

fix(net): cap child-controlled sockaddr length before reading it#129
dzerik wants to merge 2 commits into
multikernel:mainfrom
dzerik:fix/net-sockaddr-len-cap

Conversation

@dzerik

@dzerik dzerik commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Problem

A sandboxed child can force a multi-GiB allocation in the supervisor by passing an oversized sockaddr length. connect/sendto/sendmsg/sendmmsg take the address length straight from child-controlled args (addr_len = args[5], msg_namelen, both u32 up to 0xFFFFFFFF). The seccomp-notify trap fires at syscall entry, before the kernel's own addrlen > sizeof(sockaddr_storage)EINVAL check, so the length reached read_child_mem uncapped and read_child_mem_vm did vec![0u8; len].

Repro (any active net interception):

int fd = socket(AF_INET, SOCK_DGRAM, 0);
sendto(fd, buf, 1, 0, &valid_sockaddr_in, 0xFFFFFFFF);

The supervisor allocates ~4 GiB for a sockaddr that is at most 128 bytes. Under default overcommit this is a repeatable transient memory-amplification; under RLIMIT_AS / overcommit_memory=2 / low RAM the Rust allocation fails and handle_alloc_error aborts the whole supervisor — a fail-open crash of the security monitor that takes down every sandbox it runs.

Every other child-controlled read is already bounded (MAX_SEND_BUF 64 MiB, MAX_CONTROL_BUF 16 KiB, iovlen 1024, vlen 256). The sockaddr length was the one uncapped read.

Fix

Clamp each of the six sockaddr reads to MAX_SOCKADDR_LEN = size_of::<sockaddr_storage>() (128). The gate only needs the family and address bytes, and the actual send is either re-run by the kernel (Continue) or performed on-behalf with our own correctly-sized sockaddr, so no legitimate call (a real sockaddr is ≤ 128 bytes) is affected.

Test

cargo build clean; full sandlock-core lib suite green (444), including the network tests.

Notes

dzerik added 2 commits July 7, 2026 14:04
A sandboxed child can pass an arbitrary sockaddr length on connect/sendto/
sendmsg/sendmmsg (addr_len / msg_namelen are u32, up to 0xFFFFFFFF). The
seccomp-notify trap fires at syscall entry, before the kernel's own
`addrlen > sizeof(sockaddr_storage)` EINVAL check, so the length reached
read_child_mem uncapped and read_child_mem_vm did `vec![0u8; len]` — letting the
child force a multi-GiB allocation in the supervisor (OOM, or an alloc-abort of
the whole monitor under RLIMIT_AS / overcommit=2 / low RAM) for a sockaddr that
is at most 128 bytes.

Every other child-controlled read is already bounded (MAX_SEND_BUF 64 MiB,
MAX_CONTROL_BUF 16 KiB, iovlen 1024, vlen 256); the sockaddr length was the one
gap. Clamp each of the six sockaddr reads to MAX_SOCKADDR_LEN
(sizeof(sockaddr_storage) = 128) — the gate only needs the family and address
bytes, and the actual send is either re-run by the kernel (Continue) or performed
on-behalf with our own correctly-sized sockaddr, so no legitimate call is
affected.
A child sends to the allowed and the blocked host with a bogus 4 GiB `addr_len`
via raw `libc.sendto`. Without the clamp the supervisor allocated ~4 GiB and
could OOM/abort; the test asserts the run completes (the supervisor survives) and
that gating still holds on the real address bytes (allowed -> sent, blocked ->
ECONNREFUSED) despite the huge claimed length.

Verified passing against a system Python (the sandbox suite needs the interpreter
stdlib under an fs-read grant).
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.

1 participant