From c21566d90b691e966f6dce9161031198d4888ad3 Mon Sep 17 00:00:00 2001 From: Arseny Maslennikov Date: Fri, 20 Oct 2023 14:22:50 +0300 Subject: [PATCH 1/3] basic/missing_syscall: add missing_fchmodat2() Follow-up for 8b45281daa3a87b4b7a3248263cd0ba929d15596 and preparation for later commits. Since libcs are more interested in the POSIX `fchmodat(3)`, they are unlikely to provide a direct wrapper for this syscall. Thus, the headers we examine to set `HAVE_*` are picked somewhat arbitrarily. Also, hook up `try_fchmodat2()` in `test-seccomp.c`. (Also, correct that function's prototype, despite the fact that mistake would not matter in practice) Co-authored-by: Mike Yuan --- meson.build | 2 ++ src/basic/missing_syscall.h | 15 +++++++++++++++ src/test/test-seccomp.c | 23 ++++++++++++----------- 3 files changed, 29 insertions(+), 11 deletions(-) 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/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/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) { From adecfb3bc0be0def49433277fcad5333893756cc Mon Sep 17 00:00:00 2001 From: Arseny Maslennikov Date: Sat, 21 Oct 2023 11:00:00 +0300 Subject: [PATCH 2/3] basic/fs-util: prefer fchmodat2 in fchmod_opath Co-authored-by: Mike Yuan --- src/basic/fs-util.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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) From 30462563b19b92d8c6ed196d30d3cf7de90e8131 Mon Sep 17 00:00:00 2001 From: Arseny Maslennikov Date: Sat, 21 Oct 2023 11:00:00 +0300 Subject: [PATCH 3/3] nspawn-patch-uid: try fchmodat2() to restore mode of symlink Prior to this commit, if the target had been a symlink, we did nothing with it. Let's try with fchmodat2() and skip gracefully if not supported. Co-authored-by: Mike Yuan --- src/nspawn/nspawn-patch-uid.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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; }