Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

vfs: add faccessat2 syscall

POSIX defines faccessat() as having a fourth "flags" argument, while the
linux syscall doesn't have it. Glibc tries to emulate AT_EACCESS and
AT_SYMLINK_NOFOLLOW, but AT_EACCESS emulation is broken.

Add a new faccessat(2) syscall with the added flags argument and implement
both flags.

The value of AT_EACCESS is defined in glibc headers to be the same as
AT_REMOVEDIR. Use this value for the kernel interface as well, together
with the explanatory comment.

Also add AT_EMPTY_PATH support, which is not documented by POSIX, but can
be useful and is trivial to implement.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

+62 -13
+1
arch/alpha/kernel/syscalls/syscall.tbl
··· 477 477 # 545 reserved for clone3 478 478 547 common openat2 sys_openat2 479 479 548 common pidfd_getfd sys_pidfd_getfd 480 + 549 common faccessat2 sys_faccessat2
+1
arch/arm/tools/syscall.tbl
··· 451 451 435 common clone3 sys_clone3 452 452 437 common openat2 sys_openat2 453 453 438 common pidfd_getfd sys_pidfd_getfd 454 + 439 common faccessat2 sys_faccessat2
+1 -1
arch/arm64/include/asm/unistd.h
··· 38 38 #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5) 39 39 #define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800) 40 40 41 - #define __NR_compat_syscalls 439 41 + #define __NR_compat_syscalls 440 42 42 #endif 43 43 44 44 #define __ARCH_WANT_SYS_CLONE
+2
arch/arm64/include/asm/unistd32.h
··· 883 883 __SYSCALL(__NR_openat2, sys_openat2) 884 884 #define __NR_pidfd_getfd 438 885 885 __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd) 886 + #define __NR_faccessat2 439 887 + __SYSCALL(__NR_faccessat2, sys_faccessat2) 886 888 887 889 /* 888 890 * Please add new compat syscalls above this comment and update
+1
arch/ia64/kernel/syscalls/syscall.tbl
··· 358 358 # 435 reserved for clone3 359 359 437 common openat2 sys_openat2 360 360 438 common pidfd_getfd sys_pidfd_getfd 361 + 439 common faccessat2 sys_faccessat2
+1
arch/m68k/kernel/syscalls/syscall.tbl
··· 437 437 435 common clone3 __sys_clone3 438 438 437 common openat2 sys_openat2 439 439 438 common pidfd_getfd sys_pidfd_getfd 440 + 439 common faccessat2 sys_faccessat2
+1
arch/microblaze/kernel/syscalls/syscall.tbl
··· 443 443 435 common clone3 sys_clone3 444 444 437 common openat2 sys_openat2 445 445 438 common pidfd_getfd sys_pidfd_getfd 446 + 439 common faccessat2 sys_faccessat2
+1
arch/mips/kernel/syscalls/syscall_n32.tbl
··· 376 376 435 n32 clone3 __sys_clone3 377 377 437 n32 openat2 sys_openat2 378 378 438 n32 pidfd_getfd sys_pidfd_getfd 379 + 439 n32 faccessat2 sys_faccessat2
+1
arch/mips/kernel/syscalls/syscall_n64.tbl
··· 352 352 435 n64 clone3 __sys_clone3 353 353 437 n64 openat2 sys_openat2 354 354 438 n64 pidfd_getfd sys_pidfd_getfd 355 + 439 n64 faccessat2 sys_faccessat2
+1
arch/mips/kernel/syscalls/syscall_o32.tbl
··· 425 425 435 o32 clone3 __sys_clone3 426 426 437 o32 openat2 sys_openat2 427 427 438 o32 pidfd_getfd sys_pidfd_getfd 428 + 439 o32 faccessat2 sys_faccessat2
+1
arch/parisc/kernel/syscalls/syscall.tbl
··· 435 435 435 common clone3 sys_clone3_wrapper 436 436 437 common openat2 sys_openat2 437 437 438 common pidfd_getfd sys_pidfd_getfd 438 + 439 common faccessat2 sys_faccessat2
+1
arch/powerpc/kernel/syscalls/syscall.tbl
··· 527 527 435 spu clone3 sys_ni_syscall 528 528 437 common openat2 sys_openat2 529 529 438 common pidfd_getfd sys_pidfd_getfd 530 + 439 common faccessat2 sys_faccessat2
+1
arch/s390/kernel/syscalls/syscall.tbl
··· 440 440 435 common clone3 sys_clone3 sys_clone3 441 441 437 common openat2 sys_openat2 sys_openat2 442 442 438 common pidfd_getfd sys_pidfd_getfd sys_pidfd_getfd 443 + 439 common faccessat2 sys_faccessat2 sys_faccessat2
+1
arch/sh/kernel/syscalls/syscall.tbl
··· 440 440 # 435 reserved for clone3 441 441 437 common openat2 sys_openat2 442 442 438 common pidfd_getfd sys_pidfd_getfd 443 + 439 common faccessat2 sys_faccessat2
+1
arch/sparc/kernel/syscalls/syscall.tbl
··· 483 483 # 435 reserved for clone3 484 484 437 common openat2 sys_openat2 485 485 438 common pidfd_getfd sys_pidfd_getfd 486 + 439 common faccessat2 sys_faccessat2
+1
arch/x86/entry/syscalls/syscall_32.tbl
··· 442 442 435 i386 clone3 sys_clone3 443 443 437 i386 openat2 sys_openat2 444 444 438 i386 pidfd_getfd sys_pidfd_getfd 445 + 439 i386 faccessat2 sys_faccessat2
+1
arch/x86/entry/syscalls/syscall_64.tbl
··· 359 359 435 common clone3 sys_clone3 360 360 437 common openat2 sys_openat2 361 361 438 common pidfd_getfd sys_pidfd_getfd 362 + 439 common faccessat2 sys_faccessat2 362 363 363 364 # 364 365 # x32-specific system call numbers start at 512 to avoid cache impact
+1
arch/xtensa/kernel/syscalls/syscall.tbl
··· 408 408 435 common clone3 sys_clone3 409 409 437 common openat2 sys_openat2 410 410 438 common pidfd_getfd sys_pidfd_getfd 411 + 439 common faccessat2 sys_faccessat2
-1
fs/internal.h
··· 126 126 extern int build_open_flags(const struct open_how *how, struct open_flags *op); 127 127 128 128 long do_sys_ftruncate(unsigned int fd, loff_t length, int small); 129 - long do_faccessat(int dfd, const char __user *filename, int mode); 130 129 int do_fchmodat(int dfd, const char __user *filename, umode_t mode); 131 130 int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, 132 131 int flag);
+26 -8
fs/open.c
··· 394 394 return old_cred; 395 395 } 396 396 397 - long do_faccessat(int dfd, const char __user *filename, int mode) 397 + long do_faccessat(int dfd, const char __user *filename, int mode, int flags) 398 398 { 399 399 struct path path; 400 400 struct inode *inode; 401 401 int res; 402 402 unsigned int lookup_flags = LOOKUP_FOLLOW; 403 - const struct cred *old_cred; 403 + const struct cred *old_cred = NULL; 404 404 405 405 if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ 406 406 return -EINVAL; 407 407 408 - old_cred = access_override_creds(); 409 - if (!old_cred) 410 - return -ENOMEM; 408 + if (flags & ~(AT_EACCESS | AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) 409 + return -EINVAL; 410 + 411 + if (flags & AT_SYMLINK_NOFOLLOW) 412 + lookup_flags &= ~LOOKUP_FOLLOW; 413 + if (flags & AT_EMPTY_PATH) 414 + lookup_flags |= LOOKUP_EMPTY; 415 + 416 + if (!(flags & AT_EACCESS)) { 417 + old_cred = access_override_creds(); 418 + if (!old_cred) 419 + return -ENOMEM; 420 + } 411 421 412 422 retry: 413 423 res = user_path_at(dfd, filename, lookup_flags, &path); ··· 460 450 goto retry; 461 451 } 462 452 out: 463 - revert_creds(old_cred); 453 + if (old_cred) 454 + revert_creds(old_cred); 455 + 464 456 return res; 465 457 } 466 458 467 459 SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode) 468 460 { 469 - return do_faccessat(dfd, filename, mode); 461 + return do_faccessat(dfd, filename, mode, 0); 462 + } 463 + 464 + SYSCALL_DEFINE4(faccessat2, int, dfd, const char __user *, filename, int, mode, 465 + int, flags) 466 + { 467 + return do_faccessat(dfd, filename, mode, flags); 470 468 } 471 469 472 470 SYSCALL_DEFINE2(access, const char __user *, filename, int, mode) 473 471 { 474 - return do_faccessat(AT_FDCWD, filename, mode); 472 + return do_faccessat(AT_FDCWD, filename, mode, 0); 475 473 } 476 474 477 475 int ksys_chdir(const char __user *filename)
+4 -2
include/linux/syscalls.h
··· 428 428 #endif 429 429 asmlinkage long sys_fallocate(int fd, int mode, loff_t offset, loff_t len); 430 430 asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode); 431 + asmlinkage long sys_faccessat2(int dfd, const char __user *filename, int mode, 432 + int flags); 431 433 asmlinkage long sys_chdir(const char __user *filename); 432 434 asmlinkage long sys_fchdir(unsigned int fd); 433 435 asmlinkage long sys_chroot(const char __user *filename); ··· 1335 1333 return do_fchmodat(AT_FDCWD, filename, mode); 1336 1334 } 1337 1335 1338 - extern long do_faccessat(int dfd, const char __user *filename, int mode); 1336 + long do_faccessat(int dfd, const char __user *filename, int mode, int flags); 1339 1337 1340 1338 static inline long ksys_access(const char __user *filename, int mode) 1341 1339 { 1342 - return do_faccessat(AT_FDCWD, filename, mode); 1340 + return do_faccessat(AT_FDCWD, filename, mode, 0); 1343 1341 } 1344 1342 1345 1343 extern int do_fchownat(int dfd, const char __user *filename, uid_t user,
+3 -1
include/uapi/asm-generic/unistd.h
··· 855 855 __SYSCALL(__NR_openat2, sys_openat2) 856 856 #define __NR_pidfd_getfd 438 857 857 __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd) 858 + #define __NR_faccessat2 439 859 + __SYSCALL(__NR_faccessat2, sys_faccessat2) 858 860 859 861 #undef __NR_syscalls 860 - #define __NR_syscalls 439 862 + #define __NR_syscalls 440 861 863 862 864 /* 863 865 * 32 bit systems traditionally used different
+10
include/uapi/linux/fcntl.h
··· 84 84 #define DN_ATTRIB 0x00000020 /* File changed attibutes */ 85 85 #define DN_MULTISHOT 0x80000000 /* Don't remove notifier */ 86 86 87 + /* 88 + * The constants AT_REMOVEDIR and AT_EACCESS have the same value. AT_EACCESS is 89 + * meaningful only to faccessat, while AT_REMOVEDIR is meaningful only to 90 + * unlinkat. The two functions do completely different things and therefore, 91 + * the flags can be allowed to overlap. For example, passing AT_REMOVEDIR to 92 + * faccessat would be undefined behavior and thus treating it equivalent to 93 + * AT_EACCESS is valid undefined behavior. 94 + */ 87 95 #define AT_FDCWD -100 /* Special value used to indicate 88 96 openat should use the current 89 97 working directory. */ 90 98 #define AT_SYMLINK_NOFOLLOW 0x100 /* Do not follow symbolic links. */ 99 + #define AT_EACCESS 0x200 /* Test access permitted for 100 + effective IDs, not real IDs. */ 91 101 #define AT_REMOVEDIR 0x200 /* Remove directory instead of 92 102 unlinking file. */ 93 103 #define AT_SYMLINK_FOLLOW 0x400 /* Follow symbolic links. */