---
name: detect-toctou-races
description: "Detection and hardening methodology for TOCTOU (time-of-check to time-of-use) file race conditions used for local privilege escalation, where an attacker swaps a symlink, hardlink, junction, or file between a privileged process's check and its use (MITRE ATT&CK T1068, setuid case T1548.001). ATT&CK has no TOCTOU-specific technique, so detection keys off observable edges: Linux auditd SYSCALL/PATH records for symlink/link/rename, the fs.protected_* sysctls, Falco, Sysmon for Linux, osquery; Windows Sysmon FileCreate (Event ID 11) and Security object-access auditing (4663/4656). Gives the defender view of the attack, a hunt, public rule sources, and a hardening plan. Use when investigating a suspected symlink or race-condition local root, triaging a privileged process writing through a world-writable path, hunting for symlink-swap attempts in /tmp, auditing services operating on user-controllable paths, or writing TOCTOU detections."
---

# TOCTOU Race Conditions - Defensive Methodology

> **Tier 2 - methodology skill.** No bespoke UMBRASEC detection rule ships with this skill yet. It gives the defender's method, the log sources and signals, the hunt, and the hardening, and points to public rule sources. Promotion to Tier 1 (a sourced, tuned UMBRASEC rule) is tracked in [COVERAGE.md](https://github.com/umbrasec-labs/umbra-blue/blob/main/COVERAGE.md). Do not treat the content below as citing a UMBRASEC rule; there is none for this threat yet.

## TL;DR for defenders

A TOCTOU (time-of-check to time-of-use) bug is a race: a privileged process checks a path (does this file exist, who owns it, may I write it) and then acts on that path a moment later, and an unprivileged attacker swaps what the path points to in the gap between the two. The classic shape is a root or SYSTEM process that resolves a filename through a directory the attacker can write to (`/tmp`, `/var/tmp`, `/dev/shm`, `C:\ProgramData`, `%TEMP%`), letting the attacker redirect the privileged write into `/etc/passwd`, `/etc/shadow`, `/etc/sudoers.d/`, a cron directory, or a new setuid binary. You almost never catch the race itself, so detect its three edges: the precondition (a privileged process operating on a user-writable path), the attempt (an unprivileged account creating symlinks or hardlinks pointing at sensitive targets, often in a fast rename loop to widen the window), and the outcome (a privileged process writing to a file it should never touch). On Linux, turn on the `fs.protected_symlinks`/`fs.protected_hardlinks`/`fs.protected_regular`/`fs.protected_fifos` sysctls (they close most of this class outright), load auditd rules for the symlink and rename syscalls, and watch your sensitive files. Patch the known race CVEs fast.

## What it looks like, defender view

In normal operation a privileged service that uses a temp file creates it with a random name it owns, in a private directory, and the file's owner and inode do not change under it. A TOCTOU attempt breaks that invariant. In telemetry you see an unprivileged login id (`auid`) rapidly creating and deleting symlinks or hardlinks in a sticky-bit world-writable directory, the link targets being sensitive paths the user cannot normally reach, and a privileged process (`euid=0`, or a SYSTEM service) operating on a path in that same directory in the same instant. The payoff edge is a root or SYSTEM process creating or writing `/etc/passwd`, `/etc/shadow`, `/etc/cron.d/*`, `/etc/sudoers.d/*`, an `authorized_keys`, or a file that suddenly gains a setuid bit, where the written path resolved through a directory the privileged process did not own. On Windows the same idea uses NTFS junctions or object-manager symlinks (and often an oplock to stall the privileged process at the decisive moment). This skill explains the technique only to the depth needed to catch and reduce it; it ships no race-exploitation tooling.

## Log sources and signals

**Linux - Linux Audit framework (auditd)** is the primary source. Load syscall rules so the kernel emits the filesystem-manipulation records:

- `-a always,exit -F arch=b64 -S symlink,symlinkat,link,linkat,rename,renameat,renameat2 -F auid>=1000 -F auid!=unset -F key=toctou`. The decisive fields are `auid` (the immutable login uid), `uid`/`euid`, `syscall`, `exit`, and the `key`, with the `type=PATH` records carrying `name`, `inode`, and `nametype` (`CREATE`/`DELETE`/`NORMAL`). The attempt signal is a burst of these keyed records from one unprivileged `auid` against a sticky-bit directory.
- Watches on the payoff targets so the outcome is logged even when the attempt is not: `-w /etc/passwd -p wa -k sensitive`, `-w /etc/shadow -p wa -k sensitive`, `-w /etc/sudoers.d/ -p wa -k sensitive`, `-w /etc/cron.d/ -p wa -k sensitive`. A write to any of these by a process whose session `auid` is an unprivileged user is high value.
- Baseline the kernel self-protections as a control and a hunt input: `fs.protected_symlinks`, `fs.protected_hardlinks`, `fs.protected_regular`, and `fs.protected_fifos` should all be non-zero (`1` for the symlink and hardlink knobs, `1` or `2` for `protected_regular` and `protected_fifos`). A host where they are `0` is exposed to the whole sticky-directory variant of this class.

**Linux - other sources.** Falco provides runtime rules for symlink and hardlink creation over sensitive files and for writes below `/etc`; use the maintained ruleset rather than hand-rolling. Sysmon for Linux Event ID 1 (ProcessCreate) and Event ID 11 (FileCreate) give lineage and file-create context so you can tie a privileged write to its parent session. osquery `file_events` (with FSEvents/inotify configured on the sensitive directories) and `process_events` capture the create/delete churn and the privileged exec.

**Windows.** Sysmon Event ID 11 (FileCreate) and the delete events (Event ID 23/26) show a SYSTEM service creating or removing files in user-writable directories (`C:\Windows\Temp`, `C:\ProgramData`, per-user `%TEMP%`). Enable object-access auditing and set SACLs on the sensitive targets to get Security Event ID 4663 (an attempt to access an object) and 4656 (a handle request), so a write by an unexpected process is recorded. Be honest about a gap: NTFS symbolic links need `SeCreateSymbolicLinkPrivilege`, but directory junctions and mount points need no privilege and native logging rarely records their creation, so EDR file-operation telemetry usually carries that signal rather than the event log.

What normal looks like: privileged services use private, randomly named temp files they own, in directories only they (or root) can write; the `fs.protected_*` sysctls are on; symlink and hardlink creation in `/tmp` is low volume and does not target `/etc` or SUID binaries. Deviation from those baselines is the anomaly.

## Detection approach

Detect the outcome first, because it is the lowest-noise edge: alert on a write to `/etc/passwd`, `/etc/shadow`, `/etc/sudoers*`, a cron directory, or an `authorized_keys`, where the acting process's session `auid` is an unprivileged user, or on a file that newly gains a setuid/setgid bit outside your patch workflow. Then the attempt: an unprivileged `auid` creating symlinks or hardlinks whose target is a sensitive path (anything under `/etc`, `/root`, a SUID binary, another user's home) especially inside a sticky-bit world-writable directory. Then the velocity edge: a tight burst of `symlink`/`symlinkat`/`rename`/`renameat2` records from one `auid` against one directory in a sub-second window, which is the shape of widening a narrow race. On Windows, alert on a SYSTEM-owned process creating or writing files in a user-writable directory, and on junction or reparse-point creation in those directories preceding a privileged write.

A tuned UMBRASEC rule is not yet shipped for this threat. Use maintained public rules as a starting point and tune them to your baseline:

- SigmaHQ Linux rules, the auditd and file-event sets: <https://github.com/SigmaHQ/sigma/tree/master/rules/linux/auditd> and <https://github.com/SigmaHQ/sigma/tree/master/rules/linux>.
- SigmaHQ Windows file-event rules: <https://github.com/SigmaHQ/sigma/tree/master/rules/windows/file>.
- Elastic prebuilt detection rules for Linux, including file-integrity and privilege-escalation coverage: <https://github.com/elastic/detection-rules/tree/main/rules/linux>.
- Falco default ruleset (runtime symlink/hardlink-over-sensitive-files and write-below-etc rules): <https://github.com/falcosecurity/rules>.
- CWE-367 TOCTOU Race Condition as the authoritative description of the bug class, so you know which privileged file operations to prioritise: <https://cwe.mitre.org/data/definitions/367.html>.

## Threat hunt

Hypothesis: a privileged process on this host resolves a path through a directory an unprivileged user controls, and an attacker has used (or could use) a symlink swap to redirect a privileged write, without tripping a velocity alert on a single quiet attempt.

1. Inventory the precondition. List privileged (root or SYSTEM) services and scheduled jobs and find any that create, open, or write files in world-writable or user-writable directories (`/tmp`, `/var/tmp`, `/dev/shm`, `C:\ProgramData`, `%TEMP%`). On Linux, `lsof` over the suspect service plus a read of its unit and scripts; on Windows, review service accounts and the paths they touch. Each such path is a candidate TOCTOU surface.
2. Check the self-protections. Read `fs.protected_symlinks`, `fs.protected_hardlinks`, `fs.protected_regular`, and `fs.protected_fifos` on every Linux host; any set to `0` is a lead and a fix.
3. Pull the attempt edge. Over a long window, query auditd for the `toctou` key: symlink/hardlink/rename records from unprivileged `auid`s, grouped by `auid` and target directory, and surface any whose `type=PATH` `name` resolves toward `/etc`, `/root`, a SUID binary, or another user's files. Rank sub-second bursts highest.
4. Pull the outcome edge. Query the `sensitive` watch key for writes to `/etc/passwd`, `/etc/shadow`, `/etc/sudoers.d/`, and cron directories, and for new setuid bits, keeping only events whose session `auid` is not an administrator.
5. Correlate. Timeline the privileged file operation (step 1 service, or a root write in step 4) against the unprivileged link churn in step 3. A privileged write to a sensitive file that overlaps in time with an unprivileged symlink loop in the same directory is the race to chase. Pull process lineage (Sysmon Event ID 1 or auditd `ppid`) to confirm the privileged process and the attacker session.

## Hardening

The strongest fix is code-level (operate on file descriptors, not names) and kernel-level (the `fs.protected_*` sysctls), so the race cannot be won regardless of detection. The framework-mapped controls below ground in the UMBRASEC [foundation guide](https://umbrasec.dev/guide/foundation.html) Essential Eight baseline and, for the logging row, the [detection and response guide](https://umbrasec.dev/guide/detection-and-response.html); a cell is left blank where no single control maps cleanly.

| Control | What it does | Essential Eight | ISO 27001:2022 | NIST CSF 2.0 |
|---|---|---|---|---|
| Patch known race CVEs fast | Shut the window on published TOCTOU bugs in setuid binaries, container runtimes, and services that need no remote access | Patch applications; Patch operating systems | A.8.8 Technical vulnerabilities | ID.RA; PR.PS Platform Security |
| Restrict admin and service privileges | Fewer privileged processes touching user-writable paths means fewer race surfaces, and least-privilege service accounts limit the payoff | Restrict administrative privileges | A.8.2 Privileged access rights | PR.AA-05 Least privilege |
| Application control | Constrain which binaries can run, limiting the setuid/interpreter breakouts a won race tends to chain into | Application control | A.8.19 Software on systems | PR.PS Platform Security |
| Enable filesystem and audit logging | Record the attempt and outcome edges so the race is at least visible after the fact | ML2/ML3 event-log requirements | A.8.15 Logging | DE.CM Continuous monitoring |

Alongside the table, the code- and platform-level mitigations that defeat this class: have privileged code operate on a file descriptor it opened once (`fstat` the fd rather than `stat` the name, `openat` with `O_NOFOLLOW` and `O_EXCL`, `mkstemp` for temp files) instead of re-resolving a path; turn on `fs.protected_symlinks`, `fs.protected_hardlinks`, `fs.protected_regular`, and `fs.protected_fifos` (they stop a privileged process from following attacker-planted links in sticky directories); give each service a private temp namespace (systemd `PrivateTmp=yes`); keep the sticky bit on shared directories; and on Windows never let a privileged service write to a user-controllable directory, open files with `FILE_FLAG_OPEN_REPARSE_POINT` to refuse to traverse junctions, and apply the process-mitigation Redirection Guard where available. These map to MITRE D3FEND countermeasures (below) rather than to a single Essential Eight code.

## Honest limitations

You rarely see the race itself; you see its edges, and a patient attacker who wins a single quiet attempt may leave only one symlink create and one privileged write, easy to lose in a busy estate, which is why baselining privileged-service paths (step 1) and enforcing the `fs.protected_*` sysctls matter more than alerting. In-memory or intra-process races (file descriptors, shared memory, signal handlers) that never touch the filesystem produce no symlink or path telemetry at all and are out of scope for this file-focused method. Windows junction and mount-point creation needs no privilege and is poorly covered by native logging, so that variant leans entirely on EDR file-operation telemetry you may not have. auditd syscall rules add kernel overhead and can be tuned down by an attacker who already holds root, so treat the audit log as evidence up to the moment of compromise, not after. Legitimate software (package managers, build systems, backup agents) creates temp files and links in shared directories constantly and will dominate false positives until the privileged-service baseline and admin allowlist are in place. Baseline in report-only mode before you alert, and treat every threshold as a dial.

## ATT&CK and D3FEND

- MITRE ATT&CK: [T1068 Exploitation for Privilege Escalation](https://attack.mitre.org/techniques/T1068/) is the honest primary mapping; the setuid-binary variant also touches [T1548.001 Setuid and Setgid](https://attack.mitre.org/techniques/T1548/001/). ATT&CK has no TOCTOU-specific technique.
- Bug-class taxonomy: [CWE-367 Time-of-check Time-of-use (TOCTOU) Race Condition](https://cwe.mitre.org/data/definitions/367.html) and [CAPEC-29 Leveraging Time-of-Check and Time-of-Use (TOCTOU) Race Conditions](https://capec.mitre.org/data/definitions/29.html).
- MITRE D3FEND countermeasures: Local File Permissions (D3-LFP), Mandatory Access Control (D3-MAC), and System Call Filtering (D3-SCF). See <https://d3fend.mitre.org/>.
- Public rule sources: SigmaHQ Linux (<https://github.com/SigmaHQ/sigma/tree/master/rules/linux>), SigmaHQ Windows file (<https://github.com/SigmaHQ/sigma/tree/master/rules/windows/file>), Elastic detection-rules Linux (<https://github.com/elastic/detection-rules/tree/main/rules/linux>), Falco rules (<https://github.com/falcosecurity/rules>).
- A well-known filesystem TOCTOU for context: CVE-2018-15664 (Docker `cp` symlink-exchange directory-traversal race).

**Scope note.** This is a defensive skill. It explains TOCTOU race conditions only to the depth needed to detect and mitigate them, and ships no race-exploitation, symlink-swap, or window-widening tooling; UMBRASEC publishes defense, not offense.
