This does not add support for AT_EMPTY_PATH on platforms lacking it. It merely fixes bugs in handling AT_EMPTY_PATH on platforms that already have AT_EMPTY_PATH. This is complicated by the fact that glibc may define AT_EMPTY_PATH even though the underlying kernel lacks support for it, and by the fact that in recent kernels, AT_EMPTY_PATH allows the file name to be a null pointer. * lib/fchmodat.c (AT_EMPTY_PATH): Default to 0. (rpl_fchmodat): Do not mishandle AT_EMPTY_PATH if specified and if the underlying library+kernel supports it. --- ChangeLog | 12 ++++++++++++ lib/fchmodat.c | 48 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 14 deletions(-)
diff --git a/ChangeLog b/ChangeLog index 20459c0447..d78d0d1102 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2026-06-16 Paul Eggert <[email protected]> + fchmodat: don’t mishandle AT_EMPTY_PATH + This does not add support for AT_EMPTY_PATH on platforms lacking it. + It merely fixes bugs in handling AT_EMPTY_PATH on platforms + that already have AT_EMPTY_PATH. This is complicated by + the fact that glibc may define AT_EMPTY_PATH even though the + underlying kernel lacks support for it, and by the fact that + in recent kernels, AT_EMPTY_PATH allows the file name to be + a null pointer. + * lib/fchmodat.c (AT_EMPTY_PATH): Default to 0. + (rpl_fchmodat): Do not mishandle AT_EMPTY_PATH if specified + and if the underlying library+kernel supports it. + fstatat etc.: fix wrong machine code for calls Without this fix, compilers can generate incorrect machine code for functions that call to fstatat-like functions, as the diff --git a/lib/fchmodat.c b/lib/fchmodat.c index 9ac951ce68..c41768935b 100644 --- a/lib/fchmodat.c +++ b/lib/fchmodat.c @@ -48,18 +48,32 @@ orig_fchmodat (int dir, char const *file, mode_t mode, int flags) #include "issymlinkat.h" +#ifndef AT_EMPTY_PATH +# define AT_EMPTY_PATH 0 +#endif + /* Invoke chmod or lchmod on FILE, using mode MODE, in the directory open on descriptor FD. If possible, do it without changing the working directory. Otherwise, resort to using save_cwd/fchdir, then (chmod|lchmod)/restore_cwd. If either the save_cwd or the restore_cwd fails, then give a diagnostic and exit nonzero. - Note that an attempt to use a FLAG value of AT_SYMLINK_NOFOLLOW - on a system without lchmod support causes this function to fail. */ + Fail if FLAGS contains AT_SYMLINK_NOFOLLOW on a system lacking + lchmod support. */ #if HAVE_FCHMODAT int fchmodat (int dir, char const *file, mode_t mode, int flags) { + if (file && *file) + flags &= ~AT_EMPTY_PATH; + else if (! (flags & AT_EMPTY_PATH)) + { + errno = ENOENT; + return -1; + } + else if (! (flags & AT_SYMLINK_NOFOLLOW)) + return fchmod (dir, mode); + # if HAVE_NEARLY_WORKING_FCHMODAT /* Correct the trailing slash handling. */ size_t len = strlen (file); @@ -77,17 +91,21 @@ fchmodat (int dir, char const *file, mode_t mode, int flags) # endif # if NEED_FCHMODAT_NONSYMLINK_FIX - if (flags == AT_SYMLINK_NOFOLLOW) + if ((flags & ~AT_EMPTY_PATH) == AT_SYMLINK_NOFOLLOW) { # if HAVE_READLINKAT # ifdef O_PATH - /* Open a file descriptor with O_NOFOLLOW, to make sure we don't - follow symbolic links, if /proc is mounted. O_PATH is used to - avoid a failure if the file is not readable. - Cf. <https://sourceware.org/PR14578> */ - int fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC); - if (fd < 0) - return fd; + int fd; + if (flags & AT_EMPTY_PATH) + fd = dir; + else + { + /* Open with O_NOFOLLOW, so we don't follow symlinks. + See <https://sourceware.org/PR14578>. */ + fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) + return fd; + } int err; { @@ -105,7 +123,8 @@ fchmodat (int dir, char const *file, mode_t mode, int flags) err = errno == ENOENT ? -1 : errno; } - close (fd); + if (! (flags & AT_EMPTY_PATH)) + close (fd); errno = err; if (0 <= err) @@ -114,15 +133,16 @@ fchmodat (int dir, char const *file, mode_t mode, int flags) /* O_PATH + /proc is not supported. */ - if (issymlinkat (dir, file) > 0) + if (issymlinkat (dir, file ? file : "") > 0) { errno = EOPNOTSUPP; return -1; } # endif - /* Fall back on orig_fchmodat with no flags, despite a possible race. */ - flags = 0; + /* Fall back on orig_fchmodat without AT_SYMLINK_NOFOLLOW, + despite a possible race. */ + flags &= ~AT_SYMLINK_NOFOLLOW; } # endif -- 2.54.0
