Skip to content

Back to the catalog

dfir.chwriteup

Today I Learned - Protected Symlinks

A DFIR.ch blog post explaining the Linux kernel's protected_symlinks (fs.protected_symlinks) TOCTOU mitigation, then demonstrating a bypass: symlinks placed inside a subdirectory of /tmp (which doesn't inherit the sticky bit) are still followed across privilege boundaries. The author walks through reproducing CVE-2023-33865, a symlink vulnerability in RenderDoc's /tmp/RenderDoc log directory, showing how a local attacker can get an arbitrary file written with another user's privileges despite protected_symlinks being enabled, and traces the exact kernel source-code condition responsible.

TL;DR Shows how protected_symlinks fails to stop a symlink TOCTOU attack when the malicious symlink sits in a non-sticky subdirectory of /tmp, reproduced against CVE-2023-33865 in RenderDoc.
Document

What it is

A DFIR.ch walkthrough of a gap in the Linux kernel's fs.protected_symlinks mitigation, demonstrated against CVE-2023-33865 (the RenderDoc /tmp/RenderDoc symlink bug). The takeaway that generalizes beyond RenderDoc: protected_symlinks=1 only guards symlinks inside a directory that is both sticky and world-writable. A world-writable subdirectory that does not carry the sticky bit — as any attacker-created subdir of /tmp will not — sits outside the protection entirely.

How it works

When a privileged/other-user process follows a symlink, may_follow_link in the kernel returns "allowed" early on any of four conditions. The last one is the hole: the check only trips when the parent directory is both sticky and world-writable:

if ((nd->dir_mode & (S_ISVTX|S_IWOTH)) != (S_ISVTX|S_IWOTH))
    return 0;

/tmp has the sticky bit, but a subdirectory created under it (mkdir /tmp/RenderDoc) does not inherit it. So a symlink placed inside /tmp/RenderDoc fails this test and is followed even across user boundaries.

The reproduction: Thomas creates /tmp/RenderDoc, plants a symlink named like RenderDoc's log file pointing at /home/sarah/dfir.ch; when Sarah writes to that path, the write lands in her home dir. The same write attempted directly in /tmp (sticky) returns Permission denied.

Preconditions / Affected

  • protected_symlinks set to 1 (its default on many systems) — the point is that even with it enabled you are exposed.
  • A privileged or other-user process that opens/writes a predictable path inside an attacker-controllable, world-writable, non-sticky subdirectory.
  • Local attacker able to create the subdirectory and symlinks first (a plain unprivileged user).

What to do

The page states no patch or sysctl fix — the behavior is by design. The actionable engineering lesson: do not rely on protected_symlinks to cover application-created working directories. Applications that create shared subdirectories under /tmp (like RenderDoc) must set the sticky bit themselves and/or use per-user or unpredictable paths. When reviewing code, treat any world-writable subdir without S_ISVTX as unprotected against symlink races.

Caveats

  • The author confirms the mitigation does still work where the malicious symlink must live in a directory that keeps the sticky bit: the Sudo/sudoedit race (symlink in /var/tmp/) is blocked, returning permission denied. So this is a scoped gap, not a wholesale defeat.
  • The bypass depends on the vulnerable app creating a non-sticky subdirectory; it does not weaken protection for symlinks placed directly in /tmp itself.
flowchart TD
    A[Process follows symlink] --> B{protected_symlinks on?}
    B -->|no| OK[Follow allowed]
    B -->|yes| C{same owner as follower?}
    C -->|yes| OK
    C -->|no| D{parent dir sticky AND world-writable?}
    D -->|no: e.g. /tmp/RenderDoc| OK
    D -->|yes: e.g. /tmp| BLOCK[Blocked]

CVEs named here

  • CVE-2023-33865

Technologies named here

Techniques named here

  • Symlink-based TOCTOU race
  • Sticky-bit non-inheritance bypass of protected_symlinks