You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
uutils cp can be vulnerable to a classic TOCTOU as it decides whether the source is a symlink using lstat-style metadata, but later opens the source by path without O_NOFOLLOW.
An attacker who can mutate the source tree concurrently can replace a regular file with a symlink between the check and the open, causing cp to copy the symlink target’s contents.
Test / Reproduction Steps
Evidence via strace to make it easier:
In uutils, cp opens the source without O_NOFOLLOW: openat(AT_FDCWD, "/tmp/src/file", O_RDONLY|O_CLOEXEC) (no O_NOFOLLOW)
In GNU cp on Linux, the equivalent open includes O_NOFOLLOW: openat(AT_FDCWD, "/tmp/src/file", O_RDONLY|O_NOFOLLOW)
Preconditions: attacker can modify/rename entries under the source path while cp runs; victim runs cp with more read privilege than attacker; destination is attacker-readable.
Create a source directory attacker controls and a destination.
Run cp on a file inside the source while a second process repeatedly swaps that pathname between a regular file and a symlink to a sensitive target.
Observe occasional copies of the symlink target’s contents in the destination when the swap lands between the metadata check and the open.
I can't seem to replicate this behavior for GNU cp. I am getting, for GNU cp: openat(AT_FDCWD, "/tmp/src/file", O_RDONLY)
and for uutils cp: openat(AT_FDCWD, "/tmp/src/file", O_RDONLY|O_CLOEXEC)
First stat file, then open it, then stat open file, then it tries to copy it. Presumably it compares the stat of the NOFOLLOW newfstatat with the opened file.
For comparison, uutils opens the source file 4 times, never with NOFOLOW.
I've tried building GNU cp from source as well as copying different types of files to/from different locations. I'm not sure why it doesn't appear to be using NOFOLLOW on my system.
Activity
denendaden commentedon Feb 3, 2026
I can't seem to replicate this behavior for GNU cp. I am getting, for GNU cp:
openat(AT_FDCWD, "/tmp/src/file", O_RDONLY)and for uutils cp:
openat(AT_FDCWD, "/tmp/src/file", O_RDONLY|O_CLOEXEC)julian-klode commentedon Feb 4, 2026
Not sure what you are looking at but the file is opened multiple times in GNU CP:
(sorry target src/file is a bit ... weird)
First stat file, then open it, then stat open file, then it tries to copy it. Presumably it compares the stat of the NOFOLLOW newfstatat with the opened file.
For comparison, uutils opens the source file 4 times, never with NOFOLOW.
denendaden commentedon Feb 4, 2026
This is what I am seeing when I run
strace cp /tmp/src/file /tmp/dest:I've tried building GNU cp from source as well as copying different types of files to/from different locations. I'm not sure why it doesn't appear to be using NOFOLLOW on my system.
fix(cp): open source files with O_NOFOLLOW to prevent TOCTOU symlink …
cp: open source and dest with O_NOFOLLOW in no-dereference mode (uuti…
cp: open source and dest with O_NOFOLLOW in no-dereference mode (uuti…
cp: open source and dest with O_NOFOLLOW in no-dereference mode (uuti…
uucore/mv: fd-based xattr copy for cross-device moves (uutils#10014)
cp: open source and dest with O_NOFOLLOW in no-dereference mode (#10017)
cp: open source and dest with O_NOFOLLOW in no-dereference mode (uuti…
uucore/mv: fd-based xattr copy for cross-device moves (uutils#10014)