From 359baac8eb2db07ccc1ed5adc16cf8e66859af0c Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Mon, 20 Jul 2026 14:41:25 +1000 Subject: [PATCH] vfs: fail loud in the held-fd lstat no-AT_SYMLINK_NOFOLLOW arm; make the compile-check atomic Two fixes from codex review of the no-AT_FDCWD port: - vfs/stat.c: the held-dirfd vfs_lstat branch fell back to fstatat(dirfd, path, st, 0) when AT_SYMLINK_NOFOLLOW is unavailable, which FOLLOWS the leaf and breaks lstat's no-follow contract. On a system with SUPPORT_LINKS but no AT_SYMLINK_NOFOLLOW, return ENOSYS instead (mirroring the held-fd vfs_lchown arm) so a symlink-sensitive caller fails loud rather than silently following; the !SUPPORT_LINKS arm keeps fstatat(...,0) since there is nothing to follow. The CI compile-check config also undefines AT_FDCWD so no held fd is produced there; this hardens the standalone "no AT_SYMLINK_NOFOLLOW" shape. - Makefile.in: the vfs-no-at-fdcwd.o compile loop wrote every object to $@, so a mid-loop failure left a fresh-timestamped $@ and a retry could skip the check. Compile to $@.tmp and mv to $@ only after the whole loop succeeds (rm the stale target up front); clean the .tmp too. --- Makefile.in | 6 ++++-- vfs/stat.c | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Makefile.in b/Makefile.in index 0eaa16dd..1d50e2fd 100644 --- a/Makefile.in +++ b/Makefile.in @@ -91,11 +91,13 @@ all: Makefile rsync$(EXEEXT) stunnel-rsyncd.conf @MAKE_RRSYNC@ @MAKE_MAN@ # make have no pattern rules); the last object compiled is left as the target. # $(VFS_OBJ:.o=.c) is POSIX suffix substitution, portable across makes. vfs-no-at-fdcwd.o: $(VFS_OBJ:.o=.c) $(HEADERS) vfs/vfs.h vfs/vfs_internal.h + @rm -f $@ $@.tmp @for f in $(VFS_OBJ:.o=.c); do \ echo " no-AT_FDCWD compile-check: $$f"; \ $(CC) -I. -I$(srcdir) $(CFLAGS) $(CPPFLAGS) \ - -DRSYNC_TEST_NO_AT_FDCWD -c $(srcdir)/$$f -o $@ || exit 1; \ + -DRSYNC_TEST_NO_AT_FDCWD -c $(srcdir)/$$f -o $@.tmp || exit 1; \ done + @mv $@.tmp $@ .PHONY: install install: all @@ -371,7 +373,7 @@ rrsync.1: support/rrsync.1.md md-convert Makefile .PHONY: clean clean: cleantests - rm -f *~ $(OBJS) $(VFS_OBJ) libvfs.a $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_COMPILE_OBJS) $(CHECK_SYMLINKS) @MAKE_RRSYNC@ \ + rm -f *~ $(OBJS) $(VFS_OBJ) libvfs.a $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_COMPILE_OBJS) $(CHECK_COMPILE_OBJS:.o=.o.tmp) $(CHECK_SYMLINKS) @MAKE_RRSYNC@ \ git-version.h rounding rounding.h *.old rsync*.1 rsync*.5 @MAKE_RRSYNC_1@ \ *.html daemon-parm.h help-*.h default-*.h proto.h proto.h-tstamp rm -f *.gcno *.gcda lib/*.gcno lib/*.gcda zlib/*.gcno zlib/*.gcda popt/*.gcno popt/*.gcda vfs/*.gcno vfs/*.gcda diff --git a/vfs/stat.c b/vfs/stat.c index b85cf1c9..f7f4e340 100644 --- a/vfs/stat.c +++ b/vfs/stat.c @@ -153,7 +153,14 @@ int vfs_lstat(int dirfd, const char *path, STRUCT_STAT *st, int flags) } # if defined SUPPORT_LINKS && defined AT_SYMLINK_NOFOLLOW return fstatat(dirfd, path, st, AT_SYMLINK_NOFOLLOW); +# elif defined SUPPORT_LINKS + /* No AT_SYMLINK_NOFOLLOW: an fstatat via a held dirfd cannot honour + * lstat's no-follow contract, so fail loud rather than silently + * follow the leaf (mirrors the held-fd vfs_lchown ENOSYS arm); a + * symlink-sensitive caller must use the path-based form. */ + (void)dirfd; errno = ENOSYS; return -1; # else + /* No link support: nothing to follow, fstatat == lstat. */ return fstatat(dirfd, path, st, 0); # endif #else