The Linux implementation of faccessat() assumes that if __libc_enable_secure is 0 then the process's real and effective capabilities are equal and it can ignore AT_EACCESS in order to use the faccessat syscall (instead of, as documented, imperfectly emulating it with fstatat). But this isn't quite correct: if the process has CAP_SETUID and/or CAP_SETGID (e.g., if it's run as root) it can start with equal real/effective capabilities and change them later. For example, in a program run as uid 0, assuming "/etc/passwd" is a regular file with mode 0644 owned by uid 0: seteuid(-2) => 0 faccessat(AT_FDCWD, "/etc/passwd", W_OK, AT_EACCESS) => 0 open("/etc/passwd", O_WRONLY) => -1 I'm not sure how important this bug is (the usual warnings about time-of-check/time-of-use issues apply to most use cases for faccessat, and it's currently impossible to implement faccessat correctly on Linux), but I thought it should at least be on file for if/when someone else runs into it.
Interesting. I suspect faccessat should return ENOSYS if AT_EACCESS is specified. glibc does not know what kind of security modules the kernel has loaded, so any emulation will be misleading.
EINVAL would probably be the better error code.
This is similar to the problem with fchmodat—the system call does not take a flags argument: if ((flag == 0 || ((flag & ~AT_EACCESS) == 0 && ! __libc_enable_secure))) return INLINE_SYSCALL (faccessat, 3, fd, file, mode); A fix depends will need kernel support.
Linux 5.8 has the required kernel support.
Fixed in glibc 2.33. I'm marking this as security- until there is demonstrated application impact.