Repository navigation
1.1.77: no jail symlink followed on shares and mounts, own-jail container names, minimal jail env, shared display warns - #272
Merged
Conversation
files/share linked, copied, unlinked and touched the jail file by path as host root: a link at the jail file sent the host's own file to the share's host path, a symlinked directory put the hard link or the mount target into a host directory. The jail's side now goes through FsBeneath (new touch / linkIn / linkOut / parentDevice; linkOut checks the linked inode again and removes a swapped link). Mount targets (dirs/share, tmpfs, thin jail, X11 / Wayland / Pulse / seatd sockets) are made through FsBeneath, and Mount refuses a mount point that does not resolve to itself on the root path, as crated does for rootless mounts. Mount points are normalised (MountsPure:: normalizeMountPoint). socat binds the proxy socket relative to a directory held open through FsBeneath. The check-then-mount race against a running jail stays in TODO §2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
findContainer goes through RunPure::chooseContainer: crate jails only (a bastille `web` was stopped / restarted / updated), the container name among the caller's own jails only (root's `crate stop web` hit another tenant's web_pidN), a jid that names another jail than the container name is ambiguous. HTTP / control-socket start check the container name of what they were given. console / info / export drop their fallback (jail- prefix, stoi, the hostname of every jail), which opened a root login in `webapp` for `crate console web`; export names its archive by the container name. The root-path JailExec execs with PasswdPure::jailExecEnv (PATH, HOME, SHELL, USER, LOGNAME, TERM), the set crated uses too, instead of crate(1)'s whole environment. crate logs reads the jail's /var/log/messages through FsBeneath; crate import writes a new file (O_EXCL, O_NOFOLLOW) and fchowns it. CRATE_PATH_SHA256. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
The shared GUI path binds the host operator's own display socket into the jail: for X11 the host /tmp/.X11-unix, for Wayland (gui: auto on a Wayland host, or gui.mode: wayland) the host compositor's $WAYLAND_DISPLAY socket. X11 has no inter-client isolation, and the raw Wayland socket carries the compositor's privileged protocols (virtual keyboard/pointer, screencopy/export-dmabuf, data-control, foreign-toplevel, session-lock, input-inhibit). A client in one such jail can inject input into, capture and drive the GUI of every other jail on the same display and of the host itself — the shared display plane is a single trust domain, not a boundary between tenants. The X11 path warned; the Wayland bind only logged a progress line, and the X11 warning even misfired for a Wayland-only run (gui.mode: wayland routes through the shared block but binds no X11 socket, so its "read host keystrokes" wording was wrong). Move the decision into RunGuiPure::sharedDisplayWarnings so each warning fires only when that socket is actually bound, each with its own ack env var (CRATE_X11_SHARED_ACK, new CRATE_WAYLAND_SHARED_ACK). The real fix — binding a socket restricted through the compositor's wp_security_context_manager_v1 so the privileged protocols are withheld from jail clients — needs compositor support and is recorded as TODO §7. docs/trust-model.md / .uk.md gain a "Shared display plane" section, the lxqt-wayland example a security note, and the on-hardware runbook §4s a check that the warnings fire. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
Findings of the review of this branch: - crated resolved container names among root's jails only: findContainer passed its own euid, which in the daemon is 0, so stop / restart / GET by container name returned 404 for a tenant's rootless jail and start no longer answered 409. chooseContainer / findContainer take anyOwner; the HTTP and control-socket routes use it (the pool ACL judges the jail that was found), crate(1) keeps "the caller's own". - jailExecEnv dropped LANG, which cli/main.cpp keeps on purpose; the root path passes a plain locale name again. - The changelog and a comment said the jail had inherited the operator's whole environment. cli/main.cpp rebuilds environ from a short list at startup; what crossed was the host's DISPLAY, WAYLAND_DISPLAY, XAUTHORITY and XDG_RUNTIME_DIR. Text corrected. - Mount's realpath rule covered the host's way to the jail root, so a host with /var or /var/run behind a symlink could not mount at all. Only the part inside the jail root has to resolve to itself (RuntimePathsPure::crateJailRootOf). - run/user: /home is created as a directory before pw useradd -m, which on FreeBSD 13 makes it a symlink the share code then refuses. - The Wayland warning is printed once the socket is mounted, not on intent. - import: the copy loop is FsBeneath::copyToNewFile now (O_EXCL, O_NOFOLLOW, fchown through the descriptor), with a unit test. - docs/trust-model: headless / nested / gpu displays are separate, not sealed (no X access control, sockets in the host's /tmp/.X11-unix, websockify without a bind address) — caveat added, TODO §7 item. - TODO: two stale items rewritten, two "left open by 1.1.77" added; crate.5 says a share's jail path must not run through a symlink. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This release closes several ways the jail could steer what the host does:
crate console,infoandexportno longer fall back to whatever jail matches loosely;gui: auto/gui.mode: wayland) now warns that it is not a boundary between tenants, asx11shared already did.It closes these TODO items:
crate importthrough a dangling symlink;crate logs: only its symlink/FIFO part. The item stays open, narrowed to "no writer of the console logs".It adds three TODO §7 items it leaves open:
wp_security_context_manager_v1.files/share (
lib/run.cpp)<tree>/<jail file>by path, as host root.touch,linkIn,linkOutandparentDevice.linkOutlinks only a regular file. After linking it checks the inode again, and removes the link if the jail swapped the file in between.Mount targets and nmount(2)
Files:
lib/mount.cpp,lib/run.cpp,lib/run_gui.cpp,lib/mounts_pure.cpp,lib/runtime_paths_pure.cpp.Mountrefuses a mount point whose part inside the jail does not resolve to itself (realpath). The way to the jail root is the host's (RuntimePathsPure::crateJailRootOf), so a host with/varbehind a symlink still mounts.MountsPure::normalizeMountPoint), so a share with a trailing slash still mounts./etc/unbound) is refused now.run/usercreates/homeas a directory beforepw useradd -m, which on FreeBSD 13 would make it a symlink.socket_proxy (
lib/run_services.cpp)fchdir()s to the directory opened through FsBeneath and binds the bare socket name.Container names
Files:
lib/jail_query.cpp,lib/run_pure.cpp,lib/runtime_paths_pure.cpp,daemon/routes.cpp,daemon/control_socket.cpp.findContainernow usesRunPure::chooseContainer:webcould be stopped, restarted or updated before;/var/run/crate/<euid>/…). Root'scrate stop webused to hit another tenant'sweb_pidN;3was shadowed by jid 3.anyOwner: the container name matches among every tenant's jails, the pool ACL judges the jail that was found, and two tenants'webgive 409.web_pid1missed a runningweb_pid999.console / info / export (
lib/console.cpp,lib/info.cpp,lib/export.cpp)findContaineris removed. It matched:jail-<name>;stoi()could read out of the name;crate console webcould open a root login inwebapp.crate exportnow takes its default file name from the container name, not from the hostname the jail can set (which may contain/).Jail environment
Files:
lib/jail_query.cpp,lib/passwd_pure.cpp,daemon/privops_handlers.cpp.cli/main.cpprebuilds the environment from a short list at startup. But it held the host'sDISPLAY,WAYLAND_DISPLAY,XAUTHORITYandXDG_RUNTIME_DIR, which are wrong inside the jail, and it differed from what rootless gives.PasswdPure::jailExecEnv: PATH, HOME, SHELL, USER, LOGNAME, TERM, and on the root path LANG.env(1)line. Services and hooks do not get those (TODO §7).crate logs / crate import
crate logsreads the jail's/var/log/messagesthrough FsBeneath, regular files only (lib/lifecycle.cpp).crate importwrites its output with the newFsBeneath::copyToNewFile:O_EXCL | O_NOFOLLOW, owner set through the descriptor, removed again on failure (lib/import.cpp).CRATE_PATH_SHA256.Shared display plane (
lib/run_gui.cpp,lib/run_gui_pure.{h,cpp})gui: auto/gui.mode: wayland/x11shared bind the host operator's own display socket into the jail. A client in such a jail reaches the whole host display session, other jails' windows included. It is one trust domain, not a tenant boundary.RunGuiPure::sharedDisplayWarningsnow decides. The X11 warning fires only when an X11 socket is bound; the Wayland one is printed once the socket is mounted. Each has its own ack variable (CRATE_X11_SHARED_ACK, newCRATE_WAYLAND_SHARED_ACK).wp_security_context_manager_v1, is TODO §7.Docs
docs/trust-model.md/trust-model.uk.md: a new "Shared display plane" section, with the caveat that headless / nested / gpu displays are separate but not sealed.lxqt-waylandexample has a security note.Tests
make test-unitwith-Werror: 1654/1654 passed.fs_beneath_test:touch_and_open_directory,share_links,copy_to_new_file;mounts_pure_test:normalize_mount_point;run_pure_test:choose_container(withanyOwner);runtime_paths_pure_test:crate_jail_owner_uid,crate_jail_root_of;passwd_pure_test:jail_exec_env(with LANG);run_gui_pure_test:shared_display_warnings.🤖 Generated with Claude Code
https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK