diff options
author | Florian Weimer <fweimer@redhat.com> | 2020-01-22 19:01:20 +0100 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2020-02-12 08:43:59 +0100 |
commit | 752dd17443e55a4535cb9e6baa4e550ede383540 (patch) | |
tree | 9e7e6a0ab376017674ea8f20c4a9e429f4133418 | |
parent | 6b89c385d8bd0700b25bac2c2d0bebe68d5cc05d (diff) | |
download | glibc-752dd17443e55a4535cb9e6baa4e550ede383540.tar.gz glibc-752dd17443e55a4535cb9e6baa4e550ede383540.tar.xz glibc-752dd17443e55a4535cb9e6baa4e550ede383540.zip |
Linux: Emulate fchmodat with AT_SYMLINK_NOFOLLOW using O_PATH [BZ #14578]
/proc/self/fd files are special and chmod on O_PATH descriptors in that directory operates on the symbolic link itself (like lchmod).
-rw-r--r-- | sysdeps/unix/sysv/linux/fchmodat.c | 57 |
1 files changed, 47 insertions, 10 deletions
diff --git a/sysdeps/unix/sysv/linux/fchmodat.c b/sysdeps/unix/sysv/linux/fchmodat.c index c41ebb290d..719053b333 100644 --- a/sysdeps/unix/sysv/linux/fchmodat.c +++ b/sysdeps/unix/sysv/linux/fchmodat.c @@ -18,24 +18,61 @@ #include <errno.h> #include <fcntl.h> -#include <stddef.h> +#include <not-cancel.h> #include <stdio.h> -#include <string.h> -#include <unistd.h> +#include <sys/stat.h> #include <sys/types.h> -#include <alloca.h> #include <sysdep.h> +#include <unistd.h> int fchmodat (int fd, const char *file, mode_t mode, int flag) { - if (flag & ~AT_SYMLINK_NOFOLLOW) + if (flag == 0) + return INLINE_SYSCALL (fchmodat, 3, fd, file, mode); + else if (flag != AT_SYMLINK_NOFOLLOW) return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL); -#ifndef __NR_lchmod /* Linux so far has no lchmod syscall. */ - if (flag & AT_SYMLINK_NOFOLLOW) - return INLINE_SYSCALL_ERROR_RETURN_VALUE (ENOTSUP); -#endif + else + { + /* The kernel system call does not have a mode argument. + However, we can create an O_PATH descriptor and change that + via /proc (which does not resolve symbolic links). */ + + int pathfd = __openat_nocancel (fd, file, + O_PATH | O_NOFOLLOW | O_CLOEXEC); + if (pathfd < 0) + /* This may report errors such as ENFILE and EMFILE. The + caller can treat them as temporary if necessary. */ + return pathfd; + + char buf[32]; + if (__snprintf (buf, sizeof (buf), "/proc/self/fd/%d", pathfd) < 0) + { + /* This also may report strange error codes to the caller + (although snprintf really should not fail). */ + __close_nocancel (pathfd); + return -1; + } - return INLINE_SYSCALL (fchmodat, 3, fd, file, mode); + /* This operates directly on the symbolic link if it is one. + /proc/self/fd files look like symbolic links, but they are + not. (fchmod and fchmodat do not work on O_PATH descriptors, + similar to fstat before Linux 3.6.) */ + int ret = __chmod (buf, mode); + if (ret != 0) + { + if (errno == ENOENT) + /* /proc has not been mounted. Without /proc, there is no + way to upgrade the O_PATH descriptor to a full + descriptor. It is also not possible to re-open the + file without O_PATH because the file name may refer to + another file, and opening that without O_PATH may have + side effects (such as blocking, device rewinding, or + releasing POSIX locks). */ + __set_errno (EOPNOTSUPP); + } + __close_nocancel (pathfd); + return ret; + } } libc_hidden_def (fchmodat) |