operator-path-temp-dir and operator-path-partial-dir decided whether a
symlink had been followed by sampling the target directory's
st_mtime_ns, sleeping 10ms, and looking for a change. The temp file is
renamed away, so an mtime bump was the only trace left.
On a filesystem whose timestamps have 1-second granularity -- HFS+, and
it is not alone -- a change within the same second is invisible. The
delta is zero, the test concludes the symlink was not followed, and
reports the operator's OWN euid-owned symlink as refused when it was
followed correctly. Both fail that way on HFS+ while passing on APFS,
and operator-path-partial-dir is one of the failures Roland Kletzing
reported on macOS.
Pin the directory's mtime to a fixed past epoch instead, read back what
the filesystem actually stored, and ask afterwards whether it still
holds -- reading back because a filesystem may clamp or round the value,
and comparing against the requested epoch would then read an unfollowed
symlink as followed. temp-dir-symlink-injection already works this way.
This is not proof against every clock: a directory whose mtime lands
exactly on the stored sentinel would still read as unfollowed. That
needs the host clock set to 2001 or a deliberate restore, where the old
10ms delta failed on any coarse-granularity filesystem.
Verified in both directions by running as root, where the matrix also
exercises the cross-uid cells: a followed symlink moves the mtime off
the sentinel, a refused one leaves it.
The TOCTOU / symlink-race suite for the secure resolver and operator-supplied
paths: chdir/chmod/rename/mknod/source/dest symlink races, relative make_path and
symlinked-parent cases, the operator-path matrix (--temp/partial/backup-dir,
alt-dest basis, files-from, log-file, insecure-links), and the admin-file opens
(--password-file / daemon secrets / config / log-file / early-input symlinks),
plus the daemon module-confinement and chroot inner-module cases.
Co-authored-by: Omar Elsayed <omarelsayed161@gmail.com>