Skip to content

🐛 fix(unix): check XDG_RUNTIME_DIR parents - #668

Merged
gaborbernat merged 9 commits into
mainfrom
fix/runtime-dir-parent-symlink
Oct 28, 2026
Merged

gaborbernat merged 9 commits into
mainfrom
fix/runtime-dir-parent-symlink

Conversation

@gaborbernat

@gaborbernat gaborbernat commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Unix.user_runtime_dir created XDG_RUNTIME_DIR and its missing parents, then checked only the last component. When XDG_RUNTIME_DIR sat two or more levels below /tmp, another local user who created the first missing level first, as a symlink or as their own directory, chose where the victim's runtime directory went, and platformdirs returned it without a RuntimeDirWarning. After this PR, platformdirs rejects such a path with a warning and falls back as for any other rejected XDG_RUNTIME_DIR. This addresses draft advisory ANT-2026-UJJM5OMU.

The check only saw the last component

_runtime_dir_problem calls lstat(2), which leaves only a symlink in the last position unresolved. It resolves a symlink at a parent, so the owner and mode checks describe whatever directory sits at its target. Under ensure_exists, _make_private_directories had already created that directory through the link as the victim, with mode 0700, so the check passed. fs.protected_symlinks did not stop it: in the reproduction the kernel refused to follow the link as the last component but let root create directories through it as a parent.

_runtime_dir_parent_problem walks the existing parents from / down. A parent that is an entry of a world-writable directory must belong to root or the user, and no parent may be world-writable without the sticky bit. It reads the owner with lstat before following a symlink, so it still rejects a link that fs.protected_symlinks refuses to follow. user_runtime_dir runs it before creating anything and again, after the leaf check, before returning the path.

flowchart TD
    E["XDG_RUNTIME_DIR=/tmp/run/user/0, /tmp/run planted by another user"] --> B{version}
    B -- before --> C["mkdir through /tmp/run, lstat the leaf: passes"]
    C --> R["returns a path the other user can redirect"]
    B -- after --> P{"parent in a world-writable dir owned by someone else?"}
    P -- yes --> W["RuntimeDirWarning, fall back"]
    P -- no --> K["create, check leaf and parents, return"]
    classDef bad fill:#ffd6d6,stroke:#c1121f,color:#5c0000;
    classDef ok fill:#d8f3dc,stroke:#2d6a4f,color:#1b4332;
    class C,R bad;
    class W,K ok;
Loading

Behavior changes

  • An XDG_RUNTIME_DIR below another user's entry in a world-writable directory, or below a world-writable directory without the sticky bit, now raises a RuntimeDirWarning such as XDG_RUNTIME_DIR /tmp/run/user/0 is below /tmp/run, owned by uid 65534, falling back to /tmp/runtime-0. place_runtime_file falls back the same way, since its lookup goes through user_runtime_dir.
  • /run/user/<uid>, a path inside the user's home and an entry the user owns in /tmp keep working.
  • THREAT_MODEL.md now lists the parent check under the $XDG_RUNTIME_DIR guarantee, and the race and group-writable parents from the next item as accepted risks.
  • Not covered: a parent that another user plants between the first check and the creation still receives the empty directories, although platformdirs no longer returns the path. The check does not examine group-writable parents. The XDG fallback defaults and site_runtime_dir are unchanged.

Reported by Anthropic's OSS Scanner: ANT-2026-UJJM5OMU.

user_runtime_dir checked only the last component of XDG_RUNTIME_DIR and,
under ensure_exists, created every missing parent before that check. A
symlink or directory another user put at a missing parent in /tmp
decided where platformdirs created the runtime directory, and the path came
back without a warning.

Check the parents before creating anything and again before returning
the path: an entry of a world-writable directory must belong to root or
the user, and no parent may be world-writable without the sticky bit.
Otherwise warn and fall back, as for any rejected XDG_RUNTIME_DIR.

Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-UJJM5OMU.
@gaborbernat gaborbernat added bug Something isn't working security Fixes a security vulnerability labels Oct 28, 2026
@gaborbernat
gaborbernat requested a review from ofek as a code owner October 28, 2026 07:14
@gaborbernat
gaborbernat merged commit 18620ed into main Oct 28, 2026
41 checks passed
@gaborbernat
gaborbernat deleted the fix/runtime-dir-parent-symlink branch October 28, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working security Fixes a security vulnerability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant