Commit 475d4df8 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'v6.6-vfs.fchmodat2' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull fchmodat2 system call from Christian Brauner:
 "This adds the fchmodat2() system call. It is a revised version of the
  fchmodat() system call, adding a missing flag argument. Support for
  both AT_SYMLINK_NOFOLLOW and AT_EMPTY_PATH are included.

  Adding this system call revision has been a longstanding request but
  so far has always fallen through the cracks. While the kernel
  implementation of fchmodat() does not have a flag argument the libc
  provided POSIX-compliant fchmodat(3) version does. Both glibc and musl
  have to implement a workaround in order to support AT_SYMLINK_NOFOLLOW
  (see [1] and [2]).

  The workaround is brittle because it relies not just on O_PATH and
  O_NOFOLLOW semantics and procfs magic links but also on our rather
  inconsistent symlink semantics.

  This gives userspace a proper fchmodat2() system call that libcs can
  use to properly implement fchmodat(3) and allows them to get rid of
  their hacks. In this case it will immediately benefit them as the
  current workaround is already defunct because of aformentioned
  inconsistencies.

  In addition to AT_SYMLINK_NOFOLLOW, give userspace the ability to use
  AT_EMPTY_PATH with fchmodat2(). This is already possible with
  fchownat() so there's no reason to not also support it for
  fchmodat2().

  The implementation is simple and comes with selftests. Implementation
  of the system call and wiring up the system call are done as separate
  patches even though they could arguably be one patch. But in case
  there are merge conflicts from other system call additions it can be
  beneficial to have separate patches"

Link: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/fchmodat.c;h=17eca54051ee28ba1ec3f9aed170a62630959143;hb=a492b1e5ef7ab50c6fdd4e4e9879ea5569ab0a6c#l35 [1]
Link: https://git.musl-libc.org/cgit/musl/tree/src/stat/fchmodat.c?id=718f363bc2067b6487900eddc9180c84e7739f80#n28 [2]

* tag 'v6.6-vfs.fchmodat2' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  selftests: fchmodat2: remove duplicate unneeded defines
  fchmodat2: add support for AT_EMPTY_PATH
  selftests: Add fchmodat2 selftest
  arch: Register fchmodat2, usually as syscall 452
  fs: Add fchmodat2()
  Non-functional cleanup of a "__user * filename"
parents 511fb5ba 71214379
...@@ -491,3 +491,4 @@ ...@@ -491,3 +491,4 @@
559 common futex_waitv sys_futex_waitv 559 common futex_waitv sys_futex_waitv
560 common set_mempolicy_home_node sys_ni_syscall 560 common set_mempolicy_home_node sys_ni_syscall
561 common cachestat sys_cachestat 561 common cachestat sys_cachestat
562 common fchmodat2 sys_fchmodat2
...@@ -465,3 +465,4 @@ ...@@ -465,3 +465,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
#define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5) #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5)
#define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800) #define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800)
#define __NR_compat_syscalls 452 #define __NR_compat_syscalls 453
#endif #endif
#define __ARCH_WANT_SYS_CLONE #define __ARCH_WANT_SYS_CLONE
......
...@@ -909,6 +909,8 @@ __SYSCALL(__NR_futex_waitv, sys_futex_waitv) ...@@ -909,6 +909,8 @@ __SYSCALL(__NR_futex_waitv, sys_futex_waitv)
__SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node) __SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node)
#define __NR_cachestat 451 #define __NR_cachestat 451
__SYSCALL(__NR_cachestat, sys_cachestat) __SYSCALL(__NR_cachestat, sys_cachestat)
#define __NR_fchmodat2 452
__SYSCALL(__NR_fchmodat2, sys_fchmodat2)
/* /*
* Please add new compat syscalls above this comment and update * Please add new compat syscalls above this comment and update
......
...@@ -372,3 +372,4 @@ ...@@ -372,3 +372,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -451,3 +451,4 @@ ...@@ -451,3 +451,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -457,3 +457,4 @@ ...@@ -457,3 +457,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -390,3 +390,4 @@ ...@@ -390,3 +390,4 @@
449 n32 futex_waitv sys_futex_waitv 449 n32 futex_waitv sys_futex_waitv
450 n32 set_mempolicy_home_node sys_set_mempolicy_home_node 450 n32 set_mempolicy_home_node sys_set_mempolicy_home_node
451 n32 cachestat sys_cachestat 451 n32 cachestat sys_cachestat
452 n32 fchmodat2 sys_fchmodat2
...@@ -366,3 +366,4 @@ ...@@ -366,3 +366,4 @@
449 n64 futex_waitv sys_futex_waitv 449 n64 futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 n64 cachestat sys_cachestat 451 n64 cachestat sys_cachestat
452 n64 fchmodat2 sys_fchmodat2
...@@ -439,3 +439,4 @@ ...@@ -439,3 +439,4 @@
449 o32 futex_waitv sys_futex_waitv 449 o32 futex_waitv sys_futex_waitv
450 o32 set_mempolicy_home_node sys_set_mempolicy_home_node 450 o32 set_mempolicy_home_node sys_set_mempolicy_home_node
451 o32 cachestat sys_cachestat 451 o32 cachestat sys_cachestat
452 o32 fchmodat2 sys_fchmodat2
...@@ -450,3 +450,4 @@ ...@@ -450,3 +450,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -538,3 +538,4 @@ ...@@ -538,3 +538,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 nospu set_mempolicy_home_node sys_set_mempolicy_home_node 450 nospu set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -454,3 +454,4 @@ ...@@ -454,3 +454,4 @@
449 common futex_waitv sys_futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat sys_cachestat 451 common cachestat sys_cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2 sys_fchmodat2
...@@ -454,3 +454,4 @@ ...@@ -454,3 +454,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -497,3 +497,4 @@ ...@@ -497,3 +497,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -456,3 +456,4 @@ ...@@ -456,3 +456,4 @@
449 i386 futex_waitv sys_futex_waitv 449 i386 futex_waitv sys_futex_waitv
450 i386 set_mempolicy_home_node sys_set_mempolicy_home_node 450 i386 set_mempolicy_home_node sys_set_mempolicy_home_node
451 i386 cachestat sys_cachestat 451 i386 cachestat sys_cachestat
452 i386 fchmodat2 sys_fchmodat2
...@@ -373,6 +373,7 @@ ...@@ -373,6 +373,7 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
# #
# Due to a historical design error, certain syscalls are numbered differently # Due to a historical design error, certain syscalls are numbered differently
......
...@@ -422,3 +422,4 @@ ...@@ -422,3 +422,4 @@
449 common futex_waitv sys_futex_waitv 449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node 450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 common cachestat sys_cachestat 451 common cachestat sys_cachestat
452 common fchmodat2 sys_fchmodat2
...@@ -671,11 +671,20 @@ SYSCALL_DEFINE2(fchmod, unsigned int, fd, umode_t, mode) ...@@ -671,11 +671,20 @@ SYSCALL_DEFINE2(fchmod, unsigned int, fd, umode_t, mode)
return err; return err;
} }
static int do_fchmodat(int dfd, const char __user *filename, umode_t mode) static int do_fchmodat(int dfd, const char __user *filename, umode_t mode,
unsigned int flags)
{ {
struct path path; struct path path;
int error; int error;
unsigned int lookup_flags = LOOKUP_FOLLOW; unsigned int lookup_flags;
if (unlikely(flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)))
return -EINVAL;
lookup_flags = (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
if (flags & AT_EMPTY_PATH)
lookup_flags |= LOOKUP_EMPTY;
retry: retry:
error = user_path_at(dfd, filename, lookup_flags, &path); error = user_path_at(dfd, filename, lookup_flags, &path);
if (!error) { if (!error) {
...@@ -689,15 +698,21 @@ static int do_fchmodat(int dfd, const char __user *filename, umode_t mode) ...@@ -689,15 +698,21 @@ static int do_fchmodat(int dfd, const char __user *filename, umode_t mode)
return error; return error;
} }
SYSCALL_DEFINE4(fchmodat2, int, dfd, const char __user *, filename,
umode_t, mode, unsigned int, flags)
{
return do_fchmodat(dfd, filename, mode, flags);
}
SYSCALL_DEFINE3(fchmodat, int, dfd, const char __user *, filename, SYSCALL_DEFINE3(fchmodat, int, dfd, const char __user *, filename,
umode_t, mode) umode_t, mode)
{ {
return do_fchmodat(dfd, filename, mode); return do_fchmodat(dfd, filename, mode, 0);
} }
SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode) SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode)
{ {
return do_fchmodat(AT_FDCWD, filename, mode); return do_fchmodat(AT_FDCWD, filename, mode, 0);
} }
/* /*
......
...@@ -438,8 +438,10 @@ asmlinkage long sys_chdir(const char __user *filename); ...@@ -438,8 +438,10 @@ asmlinkage long sys_chdir(const char __user *filename);
asmlinkage long sys_fchdir(unsigned int fd); asmlinkage long sys_fchdir(unsigned int fd);
asmlinkage long sys_chroot(const char __user *filename); asmlinkage long sys_chroot(const char __user *filename);
asmlinkage long sys_fchmod(unsigned int fd, umode_t mode); asmlinkage long sys_fchmod(unsigned int fd, umode_t mode);
asmlinkage long sys_fchmodat(int dfd, const char __user * filename, asmlinkage long sys_fchmodat(int dfd, const char __user *filename,
umode_t mode); umode_t mode);
asmlinkage long sys_fchmodat2(int dfd, const char __user *filename,
umode_t mode, unsigned int flags);
asmlinkage long sys_fchownat(int dfd, const char __user *filename, uid_t user, asmlinkage long sys_fchownat(int dfd, const char __user *filename, uid_t user,
gid_t group, int flag); gid_t group, int flag);
asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group); asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group);
......
...@@ -820,8 +820,11 @@ __SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node) ...@@ -820,8 +820,11 @@ __SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node)
#define __NR_cachestat 451 #define __NR_cachestat 451
__SYSCALL(__NR_cachestat, sys_cachestat) __SYSCALL(__NR_cachestat, sys_cachestat)
#define __NR_fchmodat2 452
__SYSCALL(__NR_fchmodat2, sys_fchmodat2)
#undef __NR_syscalls #undef __NR_syscalls
#define __NR_syscalls 452 #define __NR_syscalls 453
/* /*
* 32 bit systems traditionally used different * 32 bit systems traditionally used different
......
...@@ -18,6 +18,7 @@ TARGETS += drivers/net/bonding ...@@ -18,6 +18,7 @@ TARGETS += drivers/net/bonding
TARGETS += drivers/net/team TARGETS += drivers/net/team
TARGETS += efivarfs TARGETS += efivarfs
TARGETS += exec TARGETS += exec
TARGETS += fchmodat2
TARGETS += filesystems TARGETS += filesystems
TARGETS += filesystems/binderfs TARGETS += filesystems/binderfs
TARGETS += filesystems/epoll TARGETS += filesystems/epoll
......
# SPDX-License-Identifier: GPL-2.0-only
/*_test
# SPDX-License-Identifier: GPL-2.0-or-later
CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined $(KHDR_INCLUDES)
TEST_GEN_PROGS := fchmodat2_test
include ../lib.mk
// SPDX-License-Identifier: GPL-2.0-or-later
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syscall.h>
#include <unistd.h>
#include "../kselftest.h"
int sys_fchmodat2(int dfd, const char *filename, mode_t mode, int flags)
{
int ret = syscall(__NR_fchmodat2, dfd, filename, mode, flags);
return ret >= 0 ? ret : -errno;
}
int setup_testdir(void)
{
int dfd, ret;
char dirname[] = "/tmp/ksft-fchmodat2.XXXXXX";
/* Make the top-level directory. */
if (!mkdtemp(dirname))
ksft_exit_fail_msg("%s: failed to create tmpdir\n", __func__);
dfd = open(dirname, O_PATH | O_DIRECTORY);
if (dfd < 0)
ksft_exit_fail_msg("%s: failed to open tmpdir\n", __func__);
ret = openat(dfd, "regfile", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (ret < 0)
ksft_exit_fail_msg("%s: failed to create file in tmpdir\n",
__func__);
close(ret);
ret = symlinkat("regfile", dfd, "symlink");
if (ret < 0)
ksft_exit_fail_msg("%s: failed to create symlink in tmpdir\n",
__func__);
return dfd;
}
int expect_mode(int dfd, const char *filename, mode_t expect_mode)
{
struct stat st;
int ret = fstatat(dfd, filename, &st, AT_SYMLINK_NOFOLLOW);
if (ret)
ksft_exit_fail_msg("%s: %s: fstatat failed\n",
__func__, filename);
return (st.st_mode == expect_mode);
}
void test_regfile(void)
{
int dfd, ret;
dfd = setup_testdir();
ret = sys_fchmodat2(dfd, "regfile", 0640, 0);
if (ret < 0)
ksft_exit_fail_msg("%s: fchmodat2(noflag) failed\n", __func__);
if (!expect_mode(dfd, "regfile", 0100640))
ksft_exit_fail_msg("%s: wrong file mode bits after fchmodat2\n",
__func__);
ret = sys_fchmodat2(dfd, "regfile", 0600, AT_SYMLINK_NOFOLLOW);
if (ret < 0)
ksft_exit_fail_msg("%s: fchmodat2(AT_SYMLINK_NOFOLLOW) failed\n",
__func__);
if (!expect_mode(dfd, "regfile", 0100600))
ksft_exit_fail_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n",
__func__);
ksft_test_result_pass("fchmodat2(regfile)\n");
}
void test_symlink(void)
{
int dfd, ret;
dfd = setup_testdir();
ret = sys_fchmodat2(dfd, "symlink", 0640, 0);
if (ret < 0)
ksft_exit_fail_msg("%s: fchmodat2(noflag) failed\n", __func__);
if (!expect_mode(dfd, "regfile", 0100640))
ksft_exit_fail_msg("%s: wrong file mode bits after fchmodat2\n",
__func__);
if (!expect_mode(dfd, "symlink", 0120777))
ksft_exit_fail_msg("%s: wrong symlink mode bits after fchmodat2\n",
__func__);
ret = sys_fchmodat2(dfd, "symlink", 0600, AT_SYMLINK_NOFOLLOW);
/*
* On certain filesystems (xfs or btrfs), chmod operation fails. So we
* first check the symlink target but if the operation fails we mark the
* test as skipped.
*
* https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html
*/
if (ret == 0 && !expect_mode(dfd, "symlink", 0120600))
ksft_exit_fail_msg("%s: wrong symlink mode bits after fchmodat2 with nofollow\n",
__func__);
if (!expect_mode(dfd, "regfile", 0100640))
ksft_exit_fail_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n",
__func__);
if (ret != 0)
ksft_test_result_skip("fchmodat2(symlink)\n");
else
ksft_test_result_pass("fchmodat2(symlink)\n");
}
#define NUM_TESTS 2
int main(int argc, char **argv)
{
ksft_print_header();
ksft_set_plan(NUM_TESTS);
test_regfile();
test_symlink();
if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
ksft_exit_fail();
else
ksft_exit_pass();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment