fix(drop-sudo): deny the runner user via socket ACLs instead of stripping group/other mode bits - #166
Open
pardhaponugoti wants to merge 1 commit into
Open
Conversation
…ping group/other mode bits restrictRootServiceSocket previously chmod-ed every root-owned service socket the runner user could write to `mode & 0o700`. On a live host that removes access for the system peers those sockets exist to serve — D-Bus clients such as systemd-resolved, journald stdout connections, docker group members. systemd-resolved (a non-root service) then fails to (re)start with "Failed to connect to system bus: Permission denied", crash-loops into the systemd start limit, and name resolution dies machine-wide. On GitHub-hosted runners this eventually starves the runner agent itself: jobs whose codex step exceeds ~45 minutes are declared "the hosted runner lost communication with the server" (observed 52-65 min across both standard and larger runners). Deny only the runner user instead, with a named-user ACL entry (setfacl -m u:<user>:---), leaving the socket mode — and therefore every system peer's access — untouched. Named-user entries also close the case a mode-only check missed: a socket whose group/other bits are already 0 but which grants the runner access through an existing ACL. When ACL tooling is unavailable the previous chmod behavior is kept as a fallback, so the security posture never regresses. The existing test contract is access-based (the fake codex binary records accessSync(W_OK) per socket) and passes unchanged; verification in the non-root phase continues to assert the dropped user cannot write any discovered socket. Fixes openai#160
|
All contributors have signed the CLA ✍️ ✅ |
Author
|
I have read the CLA Document and I hereby sign the CLA |
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.
Fixes #160
Problem
drop-sudo's Linux root phase restricts every root-owned service socket under/runthat the runner user can write to, viachmod(fd, mode & 0o700). Stripping the group/other bits doesn't just block the runner user — it locks out the system peers those sockets exist to serve: D-Bus clients such assystemd-resolved, journald stdout connections,dockergroup members.The observable consequence on a live host (details and journal excerpts in #160):
systemd-resolved, a non-root service, can no longer connect to the system bus, crash-loops withFailed to connect to system bus: Permission deniedinto the systemd start limit, and name resolution dies machine-wide. On GitHub-hosted runners this eventually starves the runner agent itself — jobs whose codex step runs past ~45 minutes die with "The hosted runner lost communication with the server" (observed at 52–65 minutes, on standard and larger runners alike, 12/12 in our data).Fix
Deny only the runner user, with a named-user ACL entry:
The socket's mode — and therefore every system peer's access — stays untouched, while the dropped user loses access exactly as before. The named-user entry also closes a case the mode check couldn't see: a socket whose group/other bits are already
0but which grants the runner access through an existing ACL entry (previously the function returned early as "already restricted" and the non-root verification phase then failed hard).If ACL tooling is unavailable (
getfaclprobe fails — missing binaries or a filesystem without ACL support), the previouschmodbehavior is kept as a fallback so the security posture never regresses; it logs the collateral it's about to cause.The TOCTOU guards are unchanged: the socket is opened
O_PATH|O_NOFOLLOW, stat-checked against the discovered device/inode, and the ACL is applied through/proc/self/fd/N, then read back and verified.Why the tests didn't need to change
The suite's contract is access-based, not mode-based: the fake codex binary records
accessSync(W_OK)per socket and the assertions require the dropped user to have lost access (dockerAccessible,serviceAccessible,worldAccessible,namedUserAclAccessibleallfalse), andverifyPrivilegedSocketsRestrictedre-checks writability as the dropped user in the non-root phase. A named-user deny ACL satisfies that contract; the full sudo-gated suite passes onubuntu-latestunchanged.Scope
One behavioral change, no interface changes,
dist/rebuilt withpnpm run build. Happy to split the fallback or adjust logging tone if maintainers prefer.