_ipc: fix AF_UNIX bind/chmod TOCTOU + symlink-pre-plant race (#298)#299
Open
web-dev0521 wants to merge 2 commits intobrowser-use:mainfrom
Open
_ipc: fix AF_UNIX bind/chmod TOCTOU + symlink-pre-plant race (#298)#299web-dev0521 wants to merge 2 commits intobrowser-use:mainfrom
web-dev0521 wants to merge 2 commits intobrowser-use:mainfrom
Conversation
Author
|
Hi, @sauravpanda , |
Author
|
Hello @sauravpanda , |
The PR moves the daemon socket from <_TMP>/bu-NAME.sock to <_TMP>/bu-NAME.d/sock to close a symlink-pre-plant race, but admin._daemon_endpoint_names() still globbed bu-*.sock — so post-merge, daemon discovery silently returned []. Existing test_admin.py only exercised the glob with hand-crafted .sock files, so the unit suite stayed green while the lifecycle was broken. - admin._daemon_endpoint_names: discover via 'bu-*.d/sock' on POSIX (require the inner sock so an empty .d/ from a half-cleanup doesn't count as a live daemon). Windows .port path unchanged. - _ipc.cleanup_endpoint: also rmdir the .d/ wrapper so /tmp doesn't fill with stale dirs. ENOTEMPTY/race both leave nothing to do. - test_admin.py: update fixtures to the new layout, plus a regression guard that anchors on _ipc._sock_path() to catch future drift. - test_ipc.py: add a short_tmp fixture (mkdtemp under /tmp). pytest's tmp_path on macOS goes under /private/var/folders/... and busts the 104-byte sun_path budget once we append bu-NAME.d/sock; the two new serve()/symlink tests fail without this. Linux's 108-byte limit was forgiving enough that the original tests happened to pass there.
Collaborator
|
btw are you a bot? |
Author
|
Hi, @sauravpanda , |
Author
How can I contact you? |
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.
Summary
Closes #298 — TOCTOU + symlink-pre-plant races on the daemon's AF_UNIX socket.
asyncio.start_unix_serverused to bind with the inherited umask, leaving the socket world-accessible (mode0o777ifumask=0) until the follow-upos.chmod(0o600)ran. Wrapping the bind inos.umask(0o077)(try/finally) guarantees the socket is created mode0o700from the kernel side; the existingchmodstays as defense-in-depth.os.path.exists()followed symlinks, so a pre-planted dangling symlink at the socket path skipped the unlink and letbind()follow it kernel-side. The fix moves the socket into a0o700private parent directory (<tmp>/bu-<NAME>.d/sock) created/verified by_ensure_private_dir(lstat-based, refuses symlinks / non-dirs / wrong-uid), and replacesos.path.exists() + unlinkwith an unconditionalos.unlink(which never follows symlinks).Net effect: once
_ensure_private_dirreturns, only our uid can place anything inside the parent dir, so the subsequent unlink+bind on the socket is race-free; and even before that,umaskensures no on-disk permission window.Changes
src/browser_harness/_ipc.py
statimport._sock_dir(name)→<tmp>/bu-<NAME>.d/._sock_pathnow returns<sock_dir>/sock(was<tmp>/bu-<NAME>.sock)._ensure_private_dir(p):mkdir(0o700), else lstat-verify it's a real dir owned by us and tighten loose perms; refuse on symlink / non-dir / foreign uid.serve()POSIX branch rewritten in 3 explicit steps: ensure private parent → unconditionalos.unlink(catchFileNotFoundError) →os.umask(0o077)aroundstart_unix_server→chmod(0o600).tests/unit/test_ipc.py — 6 new regression tests (POSIX-only via
skipif):_ensure_private_dir: creates 0o700, tightens loose perms, refuses symlinks, refuses non-directory.servesocket bound underumask=0ends up mode0o600with no looser intermediate state.serveunlinks a stale dangling symlink at the socket path (the caseos.path.existsused to silently pass).Compatibility notes
/tmp/bu-<NAME>.sockto/tmp/bu-<NAME>.d/sock. All readers of the socket path go through_ipc._sock_path()(verified:connect,cleanup_endpoint,sock_addr— daemon, admin, helpers all route through these), so the change is internal. Stale.sockfiles left over from pre-upgrade daemons are harmless leftover bytes.sun_pathlength budget is unaffected: the new layout adds 2 chars (e.g./tmp/bu-default.d/sock= 22 chars vs. 20), well under the 104/108 macOS/Linux limit. TheBH_TMP_DIR-isolated case (barebustem) becomes<BH_TMP_DIR>/bu.d/sock, still safe.Test plan
uv run --with pytest pytest tests/unit/test_ipc.py -v— 16/16 passing (10 existing + 6 new).uv run --with pytest pytest tests/unit/ -v— 72/72 passing (no regressions in admin/daemon/helpers/run suites that share the_ipcpaths).umask 0, confirm/tmp/bu-<NAME>.disdrwx------and/tmp/bu-<NAME>.d/sockissrw-------from the moment it appears./tmp/bu-<NAME>.d/sockafter the dir exists; confirm the daemon starts cleanly, the symlink is removed, and the bound path is a real socket./var/foldersdefault): confirm noOSError: AF_UNIX path too long.strace -e trace=bind,chmod,umaskto confirm the bind syscall happens with the tightened umask in effect (no observable wider-mode window).Summary by cubic
Secure AF_UNIX daemon socket binding by creating it in a private 0o700 dir and tightening permissions during bind to remove TOCTOU and symlink-pre-plant races. Also fixes daemon discovery to match the new socket layout; Windows TCP loopback is unchanged.
Bug Fixes
asyncio.start_unix_serverwithumask 0o077, thenchmod 0o600, closing the bind→chmod TOCTOU.0o700private parent dir (verified vialstat); refuse symlinks/non-dirs/foreign uid; alwaysunlinkbefore bind to defeat pre-planted symlinks.admin._daemon_endpoint_names()to discover POSIX endpoints at the new layout (require innersock; ignore empty.d/); Windows.portdiscovery unchanged._ipc.cleanup_endpoint()now alsormdirs the.d/wrapper (best-effort). Added POSIX-only regression tests, a macOS-safe short-/tmpfixture, and a discovery test tied to_ipc._sock_path().Migration
<tmp>/bu-<NAME>.d/sock. All call sites use_ipc._sock_path(), so no action needed; old.sockfiles are harmless. Windows unchanged.Written for commit aba1efc. Summary will update on new commits.