Skip to content

network: resolve chroot named unix sockets on-behalf - #337

Closed
congwang-mk wants to merge 7 commits into
mainfrom
chroot-relative-unix-path
Closed

congwang-mk wants to merge 7 commits into
mainfrom
chroot-relative-unix-path

Conversation

@congwang-mk

Copy link
Copy Markdown
Contributor

Follow-up to #328, which fixed relative named unix socket paths in non-chroot mode.

Chroot mode never moves the child's real root or cwd, yet the named unix gate checked the raw sun_path lexically and returned Continue, leaving the kernel to resolve it on the host:

  • An absolute virtual path such as /tmp/svc.sock reached the host socket of that name instead of the one in the rootfs. Where Landlock cannot gate unix connect (ABI < 9), that is an escape to host sockets under a granted prefix.
  • A relative path was checked as if anchored at / but resolved against the cwd the child was exec'ed in, so ./svc.sock from a granted cwd was refused while tmp/x.sock from an ungranted one passed.

This serves chroot connect/sendto/sendmsg/sendmmsg on-behalf like non-chroot: build the virtual path against the tracked virtual cwd, pin the inode inside the root (mount aware) with openat2(RESOLVE_IN_ROOT), and check can_write on where the pinned inode really sits. The trap stays on under chroot even with Landlock ABI v9, since the kernel cannot resolve virtual paths. As in non-chroot pre-v9, the peer's SO_PEERCRED then names the supervisor.

Tests: a new unix-send rootfs-helper command and three chroot integration tests (absolute and relative stream connects, relative datagram, relative path outside the grant, symlink out of the grant). All fail on main and pass here.

Chroot mode never moves the child's real root or cwd, yet the named unix
gate checked the raw sun_path lexically and returned Continue, leaving the
kernel to resolve it on the host. An absolute virtual path therefore
reached the host's socket of that name (an escape where Landlock cannot
gate unix connect), and a relative one was checked as if anchored at /
but resolved against the cwd the child was exec'ed in, so ./svc.sock from
a granted cwd was refused while tmp/x.sock from an ungranted one passed.

Serve chroot connect/sendto/sendmsg/sendmmsg on-behalf like non-chroot:
build the virtual path against the tracked virtual cwd, pin the inode
inside the root (mount aware), and check write access on where it really
sits. The trap now stays on under chroot even with Landlock ABI v9.

Signed-off-by: Cong Wang <cwang@multikernel.io>
@congwang-mk
congwang-mk force-pushed the chroot-relative-unix-path branch from 55acccc to 7354bfd Compare October 31, 2026 20:51
A synchronous operation can outlive the future waiting for it. Using the
caller's blocking pool lets stalled work consume its threads and delay
runtime shutdown, while releasing admission on timeout would allow new
work to accumulate behind calls that are still running.

Add process-wide Tokio blocking pools with admission owned by each native
closure until it returns. Reject saturation with EAGAIN and give filesystem
work separate capacity so stalled external peers cannot take its workers.
Dropping a waiter stops waiting only; it does not cancel the native call.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Unix and IP connects execute synchronously after policy approval. A slow
peer can therefore hold up the notification loop and prevent unrelated
trapped syscalls from making progress.

Prepare a lazy blocking action for both connect paths and run it through
the bounded pool once approved and admitted. The closure keeps the socket
and pinned path alive until connect returns, and a rejected operation
never starts the native call.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Awaiting an FFI callback inline still holds up notification intake even
when the callback runs on a blocking thread. Terminal deferral cannot
replace that wait because Continue must reach the remaining handlers.

Add a suspended action that parks the notification and resumes its handler
chain when the work finishes. Keep explicit deferral terminal, and use the
bounded blocking pool for FFI callbacks. Retain the listener and callback
owner in native work, and translate callback output there so a timed-out
waiter cannot free userdata or strand an injected fd. Track native access
to child memory so exec argument validation does not overlap it.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Chroot path lookup and COW filesystem operations perform synchronous I/O
while dispatching a notification. Offloading only the copy leaves those
lookups on the notification loop and can release COW state before a copy
whose waiter was dropped has finished.

Suspend these handlers on the isolated filesystem pool and keep the owned
COW state lock through the whole operation, including copying and quota
bookkeeping. Run copies directly on that worker and fail a write with EIO
when copying fails. Route image helper work through the same pool.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Resuming the handler chain in a parked task lets it start native memory
access while the notification loop validates exec arguments. Shared
admission also lets stalled callbacks exclude filesystem work, and a fork
slot reserved before suspension can leak when the request never resumes.

Return suspended dispatch state to the notification loop before advancing
the remaining handlers, keeping one deadline across suspensions. Track
native memory access separately from admission and give filesystem waits
their own capacity. Drain completed work during notification intake so a
busy child cannot indefinitely postpone it.

Reserve fork slots only after handlers and approval allow Continue, roll
back failed responses, and refuse terminal deferral for fork-like calls
because it bypasses accounting.

Signed-off-by: Cong Wang <cwang@multikernel.io>
@congwang-mk
congwang-mk force-pushed the chroot-relative-unix-path branch from fc422d3 to 31366f2 Compare November 2, 2026 02:50
A timed-out callback can keep running after its sandbox exits. Retaining
the handler container does not protect caller-owned userdata, whose
public lifetime ends when the FFI run returns.

Keep a callback guard for each registered handler through the run. Wait
for active callbacks before releasing their userdata and remove the
callback so queued work cannot access it afterward. Preserve concurrent
callback execution while keeping teardown outside the notification loop.

Cover abandoned callbacks with borrowed userdata and queued callbacks
that have not started. Document that a callback which never returns also
prevents the FFI run from completing.

Signed-off-by: Cong Wang <cwang@multikernel.io>
@congwang-mk

Copy link
Copy Markdown
Contributor Author

Superseded by #340, which carries this PR's chroot named unix socket fix unchanged as its first commit and replaces the off-loop dispatch rework with a simpler design.

@congwang-mk congwang-mk closed this Nov 2, 2026
@congwang-mk
congwang-mk deleted the chroot-relative-unix-path branch November 2, 2026 22:29
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