You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On Windows the hook relay's event write still has an open (but narrowed)
check-to-write window. src/bmad_loop/data/bmad_loop_hook.py::_write_event
refuses a redirected events/ dir (symlink or junction), then — on the fallback
path only — re-resolves the directory by path for the os.open and the os.replace.
PR #493 narrowed this: the redirect check runs again after the payload is
written and before it is published, so a junction that is swapped in and left
in place is refused and the temp file removed. What remains is a
swap-and-restore inside the window: replace events/ with a junction, let the
relay create its temp file inside the attacker's directory, then restore the
real directory before the post-write check runs.
Why it is not already fixed
POSIX closes this properly — the create+rename are anchored to a dir_fd opened O_RDONLY|O_DIRECTORY|O_NOFOLLOW, so no later path resolution happens.
Windows has no equivalent reachable from the standard library: os.supports_dir_fd is empty there (dir_fd is implemented with the POSIX *at
calls), and Python exposes no handle-relative open. Closing it properly needs a
directory handle from CreateFileW with FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, and then NtCreateFile with OBJECT_ATTRIBUTES.RootDirectory set to it — i.e. ctypes
against ntdll, inside a relay that is stdlib-only by contract and runs
under whatever interpreter the host happens to have. That is disproportionate to
the residual risk and carries its own hazard: a bug in it stops hook delivery on
Windows, which stalls every run — the exact harm being defended against.
Residual risk
Low. The attacker must win a precisely-timed race against a hook that fires at
moments they do not control, and must restore the real directory immediately
after. Failing the race in either direction is safe: a persistent junction is
refused (before or after the write), and a refusal degrades to the same session_timeout_min path the run would have hit anyway. No silent redirect
survives unless the restore is perfectly timed.
Options if this is ever picked up
Accept and document (status quo). The narrowed window is recorded in the _write_event docstring.
ctypes + NtCreateFile handle-relative open on Windows. Correct, but a
large Windows-CI-only-testable surface in a stdlib-only relay.
What
On Windows the hook relay's event write still has an open (but narrowed)
check-to-write window.
src/bmad_loop/data/bmad_loop_hook.py::_write_eventrefuses a redirected
events/dir (symlink or junction), then — on the fallbackpath only — re-resolves the directory by path for the
os.openand theos.replace.PR #493 narrowed this: the redirect check runs again after the payload is
written and before it is published, so a junction that is swapped in and left
in place is refused and the temp file removed. What remains is a
swap-and-restore inside the window: replace
events/with a junction, let therelay create its temp file inside the attacker's directory, then restore the
real directory before the post-write check runs.
Why it is not already fixed
POSIX closes this properly — the create+rename are anchored to a
dir_fdopenedO_RDONLY|O_DIRECTORY|O_NOFOLLOW, so no later path resolution happens.Windows has no equivalent reachable from the standard library:
os.supports_dir_fdis empty there (dir_fd is implemented with the POSIX*atcalls), and Python exposes no handle-relative open. Closing it properly needs a
directory handle from
CreateFileWwithFILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, and thenNtCreateFilewithOBJECT_ATTRIBUTES.RootDirectoryset to it — i.e.ctypesagainst
ntdll, inside a relay that is stdlib-only by contract and runsunder whatever interpreter the host happens to have. That is disproportionate to
the residual risk and carries its own hazard: a bug in it stops hook delivery on
Windows, which stalls every run — the exact harm being defended against.
Residual risk
Low. The attacker must win a precisely-timed race against a hook that fires at
moments they do not control, and must restore the real directory immediately
after. Failing the race in either direction is safe: a persistent junction is
refused (before or after the write), and a refusal degrades to the same
session_timeout_minpath the run would have hit anyway. No silent redirectsurvives unless the restore is perfectly timed.
Options if this is ever picked up
_write_eventdocstring.ctypes+NtCreateFilehandle-relative open on Windows. Correct, but alarge Windows-CI-only-testable surface in a stdlib-only relay.
removes the attacker's ability to touch
events/at all. This overlaps withthe direction of
initvendors the hook relay into the agent-writable workspace, turning a file-write primitive into unattended persistent code execution #461 Phase 2 and is probably the cheapest real close.Option 3 is the one worth evaluating first.
Follow-up from #461 Phase 1 (PR #493), raised by an automated reviewer on that PR.