diff --git a/meson.build b/meson.build index 7f5d465783..4f50b32cdf 100644 --- a/meson.build +++ b/meson.build @@ -568,6 +568,8 @@ foreach ident : [ ['memfd_create', '''#include '''], ['gettid', '''#include #include '''], + ['fchmodat2', '''#include + #include '''], # no known header declares fchmodat2 ['pivot_root', '''#include #include '''], # no known header declares pivot_root ['ioprio_get', '''#include '''], # no known header declares ioprio_get diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a9336f1a67..f5a1a8edbf 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -288,8 +288,22 @@ int fchmod_umask(int fd, mode_t m) { int fchmod_opath(int fd, mode_t m) { /* This function operates also on fd that might have been opened with - * O_PATH. Indeed fchmodat() doesn't have the AT_EMPTY_PATH flag like - * fchownat() does. */ + * O_PATH. The tool set we have is non-intuitive: + * - fchmod(2) only operates on open files (i. e., fds with an open file description); + * - fchmodat(2) does not have a flag arg like fchownat(2) does, so no way to pass AT_EMPTY_PATH; + * + it should not be confused with the libc fchmodat(3) interface, which adds 4th flag argument, + * but does not support AT_EMPTY_PATH (only supports AT_SYMLINK_NOFOLLOW); + * - fchmodat2(2) supports all the AT_* flags, but is still very recent. + * + * We try to use fchmodat2(), and, if it is not supported, resort + * to the /proc/self/fd dance. */ + + assert(fd >= 0); + + if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0) + return 0; + if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */ + return -errno; if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) { if (errno != ENOENT) diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h index 83ec137fa5..d795efd8f2 100644 --- a/src/basic/missing_syscall.h +++ b/src/basic/missing_syscall.h @@ -32,6 +32,21 @@ /* ======================================================================= */ +#if !HAVE_FCHMODAT2 +static inline int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) { +# ifdef __NR_fchmodat2 + return syscall(__NR_fchmodat2, dirfd, path, mode, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define fchmodat2 missing_fchmodat2 +#endif + +/* ======================================================================= */ + #if !HAVE_PIVOT_ROOT static inline int missing_pivot_root(const char *new_root, const char *put_old) { return syscall(__NR_pivot_root, new_root, put_old); diff --git a/src/nspawn/nspawn-patch-uid.c b/src/nspawn/nspawn-patch-uid.c index 66663adc2b..063995dd6b 100644 --- a/src/nspawn/nspawn-patch-uid.c +++ b/src/nspawn/nspawn-patch-uid.c @@ -11,6 +11,7 @@ #include "fileio.h" #include "fs-util.h" #include "missing_magic.h" +#include "missing_syscall.h" #include "nspawn-def.h" #include "nspawn-patch-uid.h" #include "stat-util.h" @@ -239,14 +240,18 @@ static int patch_fd(int fd, const char *name, const struct stat *st, uid_t shift /* The Linux kernel alters the mode in some cases of chown(). Let's undo this. */ if (name) { + /* It looks like older glibc (before 2016) did not support AT_SYMLINK_NOFOLLOW. */ if (!S_ISLNK(st->st_mode)) - r = fchmodat(fd, name, st->st_mode, 0); - else /* AT_SYMLINK_NOFOLLOW is not available for fchmodat() */ - r = 0; + r = RET_NERRNO(fchmodat(fd, name, st->st_mode, 0)); + else { + r = RET_NERRNO(fchmodat2(fd, name, st->st_mode, AT_SYMLINK_NOFOLLOW)); + if (IN_SET(r, -ENOSYS, -EPERM)) + r = 0; + } } else - r = fchmod(fd, st->st_mode); + r = RET_NERRNO(fchmod(fd, st->st_mode)); if (r < 0) - return -errno; + return r; changed = true; } diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c index 56c4b3fd87..279a155cb0 100644 --- a/src/test/test-seccomp.c +++ b/src/test/test-seccomp.c @@ -21,7 +21,7 @@ #include "macro.h" #include "memory-util.h" #include "missing_sched.h" -#include "missing_syscall_def.h" +#include "missing_syscall.h" #include "nsflags.h" #include "nulstr-util.h" #include "process-util.h" @@ -1007,21 +1007,22 @@ static int real_open(const char *path, int flags, mode_t mode) { #endif } -static int try_fchmodat2(int dirfd, const char *path, int flags, mode_t mode) { +static int try_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) { + int r; + /* glibc does not provide a direct wrapper for fchmodat2(). Let's hence define our own wrapper for * testing purposes that calls the real syscall, on architectures and in environments where * SYS_fchmodat2 is defined. Otherwise, let's just fall back to the glibc fchmodat() call. */ -#if defined __NR_fchmodat2 && __NR_fchmodat2 >= 0 - int r; - r = (int) syscall(__NR_fchmodat2, dirfd, path, flags, mode); + /* Not supported by fchmodat() */ + assert_se(!FLAGS_SET(flags, AT_EMPTY_PATH)); + + r = RET_NERRNO(fchmodat2(dirfd, path, mode, flags)); + if (r != -ENOSYS) + return r; + /* The syscall might still be unsupported by kernel or libseccomp. */ - if (r < 0 && errno == ENOSYS) - return fchmodat(dirfd, path, flags, mode); - return r; -#else - return fchmodat(dirfd, path, flags, mode); -#endif + return RET_NERRNO(fchmodat(dirfd, path, mode, flags)); } TEST(restrict_suid_sgid) {