mirror of
git://sourceware.org/git/glibc.git
synced 2025-03-06 20:58:33 +01:00
linux: Use fchmodat2 on fchmod for flags different than 0 (BZ 26401)
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_NOFOLLOW 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 <fweimer@redhat.com>
This commit is contained in:
parent
c52c2c32db
commit
65341f7bbe
3 changed files with 80 additions and 58 deletions
|
@ -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. */
|
/* The error code from the openat fallback leaks out. */
|
||||||
if (errno != ENFILE && errno != EMFILE)
|
if (errno != ENFILE && errno != EMFILE)
|
||||||
TEST_COMPARE (errno, EOPNOTSUPP);
|
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. */
|
/* Close the descriptors. */
|
||||||
for (int *pfd = fd_list_begin (&fd_list); pfd < fd_list_end (&fd_list);
|
for (int *pfd = fd_list_begin (&fd_list); pfd < fd_list_end (&fd_list);
|
||||||
|
|
|
@ -26,66 +26,80 @@
|
||||||
#include <sysdep.h>
|
#include <sysdep.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if !__ASSUME_FCHMODAT2
|
||||||
|
static int
|
||||||
|
fchmodat_fallback (int fd, const char *file, mode_t mode, int flag)
|
||||||
|
{
|
||||||
|
if (flag != AT_SYMLINK_NOFOLLOW)
|
||||||
|
return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
|
||||||
|
|
||||||
|
/* 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_NOFOLLOW | 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;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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
|
int
|
||||||
fchmodat (int fd, const char *file, mode_t mode, int flag)
|
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)
|
if (flag == 0)
|
||||||
return INLINE_SYSCALL (fchmodat, 3, fd, file, mode);
|
return INLINE_SYSCALL_CALL (fchmodat, fd, file, mode);
|
||||||
else if (flag != AT_SYMLINK_NOFOLLOW)
|
|
||||||
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,
|
int r = INLINE_SYSCALL_CALL (fchmodat2, fd, file, mode, flag);
|
||||||
O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
if (r != 0 && errno == ENOSYS)
|
||||||
if (pathfd < 0)
|
return fchmodat_fallback (fd, file, mode, flag);
|
||||||
/* This may report errors such as ENFILE and EMFILE. The
|
return r;
|
||||||
caller can treat them as temporary if necessary. */
|
#endif
|
||||||
return pathfd;
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
libc_hidden_def (fchmodat)
|
libc_hidden_def (fchmodat)
|
||||||
|
|
|
@ -252,4 +252,12 @@
|
||||||
# define __ASSUME_CLONE3 0
|
# define __ASSUME_CLONE3 0
|
||||||
#endif
|
#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 */
|
#endif /* kernel-features.h */
|
||||||
|
|
Loading…
Add table
Reference in a new issue