Skip to content

Commit

Permalink
linux: Use fchmodat2 on fchmod for flags different than 0 (BZ 26401)
Browse files Browse the repository at this point in the history
Linux 6.6 (09da082b07bbae1c) added support for fchmodat2, which has
similar semantics as fchmodat with an extra flag argument.  This
allows fchmodat to implement AT_SYMLINK_ and AT_EMPTY_PATH
without the need for procfs.

The syscall is registered on all architectures (with value of 452
except on alpha which is 562, commit 78252deb023cf087).

The tst-lchmod.c requires a small fix where fchmodat checks two
contradictory assertions ('(st.st_mode & 0777) == 2' and
'(st.st_mode & 0777) == 3').

Checked on x86_64-linux-gnu on a 6.6 kernel.

Reviewed-by: Florian Weimer <[email protected]>
  • Loading branch information
zatrazz committed Nov 20, 2023
1 parent c52c2c3 commit 65341f7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 55 deletions.
4 changes: 2 additions & 2 deletions io/tst-lchmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ test_1 (bool do_relative_path, int (*chmod_func) (int fd, const char *, mode_t,
/* The error code from the openat fallback leaks out. */
if (errno != ENFILE && errno != EMFILE)
TEST_COMPARE (errno, EOPNOTSUPP);
xstat (path_file, &st);
TEST_COMPARE (st.st_mode & 0777, 3);
}
xstat (path_file, &st);
TEST_COMPARE (st.st_mode & 0777, 3);

/* Close the descriptors. */
for (int *pfd = fd_list_begin (&fd_list); pfd < fd_list_end (&fd_list);
Expand Down
120 changes: 67 additions & 53 deletions sysdeps/unix/sysv/linux/fchmodat.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,66 +26,80 @@
#include <sysdep.h>
#include <unistd.h>

int
fchmodat (int fd, const char *file, mode_t mode, int flag)
#if !__ASSUME_FCHMODAT2
static int
fchmodat_fallback (int fd, const char *file, mode_t mode, int flag)
{
if (flag == 0)
return INLINE_SYSCALL (fchmodat, 3, fd, file, mode);
else if (flag != AT_SYMLINK_)
if (flag != AT_SYMLINK_)
return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
else
{
/* The kernel system call does not have a mode argument.
However, we can create an O_PATH descriptor and change that
via /proc (which does not resolve symbolic links). */

int pathfd = __openat_nocancel (fd, file,
O_PATH | O_ | O_CLOEXEC);
if (pathfd < 0)
/* This may report errors such as ENFILE and EMFILE. The
caller can treat them as temporary if necessary. */
return pathfd;
/* The kernel system call does not have a mode argument.
However, we can create an O_PATH descriptor and change that
via /proc (which does not resolve symbolic links). */

/* Use fstatat because fstat does not work on O_PATH descriptors
before Linux 3.6. */
struct __stat64_t64 st;
if (__fstatat64_time64 (pathfd, "", &st, AT_EMPTY_PATH) != 0)
{
__close_nocancel (pathfd);
return -1;
}
int pathfd = __openat_nocancel (fd, file,
O_PATH | O_ | O_CLOEXEC);
if (pathfd < 0)
/* This may report errors such as ENFILE and EMFILE. The
caller can treat them as temporary if necessary. */
return pathfd;

/* Some Linux versions with some file systems can actually
change symbolic link permissions via /proc, but this is not
intentional, and it gives inconsistent results (e.g., error
return despite mode change). The expected behavior is that
symbolic link modes cannot be changed at all, and this check
enforces that. */
if (S_ISLNK (st.st_mode))
{
__close_nocancel (pathfd);
__set_errno (EOPNOTSUPP);
return -1;
}
/* Use fstatat because fstat does not work on O_PATH descriptors
before Linux 3.6. */
struct __stat64_t64 st;
if (__fstatat64_time64 (pathfd, "", &st, AT_EMPTY_PATH) != 0)
{
__close_nocancel (pathfd);
return -1;
}

/* For most file systems, fchmod does not operate on O_PATH
descriptors, so go through /proc. */
struct fd_to_filename filename;
int ret = __chmod (__fd_to_filename (pathfd, &filename), mode);
if (ret != 0)
{
if (errno == ENOENT)
/* /proc has not been mounted. Without /proc, there is no
way to upgrade the O_PATH descriptor to a full
descriptor. It is also not possible to re-open the
file without O_PATH because the file name may refer to
another file, and opening that without O_PATH may have
side effects (such as blocking, device rewinding, or
releasing POSIX locks). */
__set_errno (EOPNOTSUPP);
}
/* Some Linux versions with some file systems can actually
change symbolic link permissions via /proc, but this is not
intentional, and it gives inconsistent results (e.g., error
return despite mode change). The expected behavior is that
symbolic link modes cannot be changed at all, and this check
enforces that. */
if (S_ISLNK (st.st_mode))
{
__close_nocancel (pathfd);
return ret;
__set_errno (EOPNOTSUPP);
return -1;
}

/* For most file systems, fchmod does not operate on O_PATH
descriptors, so go through /proc. */
struct fd_to_filename filename;
int ret = __chmod (__fd_to_filename (pathfd, &filename), mode);
if (ret != 0)
{
if (errno == ENOENT)
/* /proc has not been mounted. Without /proc, there is no
way to upgrade the O_PATH descriptor to a full
descriptor. It is also not possible to re-open the
file without O_PATH because the file name may refer to
another file, and opening that without O_PATH may have
side effects (such as blocking, device rewinding, or
releasing POSIX locks). */
__set_errno (EOPNOTSUPP);
}
__close_nocancel (pathfd);
return ret;
}
#endif

int
fchmodat (int fd, const char *file, mode_t mode, int flag)
{
#if __ASSUME_FCHMODAT2
return INLINE_SYSCALL_CALL (fchmodat2, fd, file, mode, flag);
#else
if (flag == 0)
return INLINE_SYSCALL_CALL (fchmodat, fd, file, mode);

int r = INLINE_SYSCALL_CALL (fchmodat2, fd, file, mode, flag);
if (r != 0 && errno == ENOSYS)
return fchmodat_fallback (fd, file, mode, flag);
return r;
#endif
}
libc_hidden_def (fchmodat)
8 changes: 8 additions & 0 deletions sysdeps/unix/sysv/linux/kernel-features.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,12 @@
# define __ASSUME_CLONE3 0
#endif

/* The fchmodat2 system call was introduced across all architectures
in Linux 6.6. */
#if __LINUX_KERNEL_VERSION >= 0x060600
# define __ASSUME_FCHMODAT2 1
#else
# define __ASSUME_FCHMODAT2 0
#endif

#endif /* kernel-features.h */

0 comments on commit 65341f7

Please sign in to comment.