From 752dd17443e55a4535cb9e6baa4e550ede383540 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Wed, 22 Jan 2020 19:01:20 +0100 Subject: 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). --- sysdeps/unix/sysv/linux/fchmodat.c | 57 +++++++++++++++++++++++++++++++------- 1 file 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 #include -#include +#include #include -#include -#include +#include #include -#include #include +#include 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) -- cgit v1.2.1