security: harden session directory, stale-socket unlink, and clear path#38
Open
babs wants to merge 3 commits into
Open
security: harden session directory, stale-socket unlink, and clear path#38babs wants to merge 3 commits into
babs wants to merge 3 commits into
Conversation
Verify the session directory ($HOME/.cache/<progname> or the /tmp/.<progname>-<uid> fallback) is a real directory owned by the current uid with mode 0700 after mkdir(). The mkdir() return value was previously ignored, so on the /tmp fallback path a hostile local user could pre-create /tmp/.<progname>-<uid>/ with permissive modes (or as a symlink) and intercept sockets and logs written by the victim. Also open the session log with O_NOFOLLOW so a pre-placed symlink at <sockname>.log cannot redirect master writes into an arbitrary file.
cmd_open() and the legacy -A mode unlink sockname unconditionally when connect() returns ECONNREFUSED. With the session directory now validated as user-owned 0700 this is no longer reachable cross-user, but defense in depth is cheap: lstat the path and only unlink when it really is a unix socket owned by the current uid, closing the TOCTOU between the failed connect and the unlink.
cmd_clear stored a const pointer returned by getenv() into the global char *sockname via a cast that drops const — undefined behavior if any future code path mutates sockname, and fragile in general. Duplicate the chosen ancestry segment into owned storage instead. Open the log with O_NOFOLLOW so a pre-placed symlink at <sockname>.log cannot redirect the truncate onto an arbitrary user-writable file.
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.
Three related local-attack hardenings, one per commit. Each is independent and small enough to revert in isolation.
1. Validate session directory ownership and mode
expand_sockname()callsmkdir(dir, 0700)and ignores the return value. On the/tmp/.<progname>-<uid>fallback path (taken when\$HOMEis unset or/), a hostile local user can pre-create that directory with permissive modes (or as a symlink) before the victim runs atch — every subsequent socket and.loglands under attacker control.Fix: after
mkdir,lstatthe directory and refuse to proceed unless it is a real directory (not a symlink) owned by the current uid with mode0700.Also adds
O_NOFOLLOWto the master's session-log open. Defense in depth: with the directory check in place the dir contents are already user-only, butO_NOFOLLOWalso blocks any same-uid process that plants a symlink betweenmkdirandopen.2. Stat before unlinking stale session socket
cmd_openand the legacy-Amode callunlink(sockname)unconditionally whenconnect()returnsECONNREFUSED. With the directory check this is no longer cross-user-reachable, but the TOCTOU between the failed connect and the unlink is still a sharp edge.Fix:
lstatthe path and unlink only if it's really a unix socket owned by the current uid.3. Copy session name from env in
cmd_clear,O_NOFOLLOWthe logcmd_clearstored aconst char *returned bygetenv()into the globalchar *socknamevia a cast that dropsconst— undefined behavior if any future code path mutatessockname. Duplicate the chosen ancestry segment into owned storage instead.Open the log with
O_NOFOLLOWfor the same reason as commit 1: refuse to truncate via a symlink.Test plan
makeclean (no new warnings)sh tests/test.sh ./atch— 228/229 (the one failing test fails onmaintoo; unrelated)~/.cache/atchmode 0755 → atch refuses with clear message~/.cache/atch/x.log→/tmp/targetsymlink, runatch clear x→ refused (ELOOP), target untouched~/.cache/atch/x.log→/tmp/targetsymlink, runatch start x …→ log open silently disabled (existing best-effort behavior), target untouchedNotes
\$HOME/.cache/<progname>/path with default mode0700.MSG_KILL, raw escape replay on attach/tail,tail -fblind to log rotation) are not included here; happy to send follow-up PRs if you want them.