This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[PATCH 3/5] Linux: Emulate fchmodat with AT_SYMLINK_NOFOLLOW using O_PATH [BZ #14578]
- From: Florian Weimer <fweimer at redhat dot com>
- To: libc-alpha at sourceware dot org
- Date: Wed, 22 Jan 2020 21:03:34 +0100
- Subject: [PATCH 3/5] Linux: Emulate fchmodat with AT_SYMLINK_NOFOLLOW using O_PATH [BZ #14578]
- References: <cover.1579723048.git.fweimer@redhat.com>
/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 | 61 +++++++++++++++++++++++++-----
1 file changed, 51 insertions(+), 10 deletions(-)
diff --git a/sysdeps/unix/sysv/linux/fchmodat.c b/sysdeps/unix/sysv/linux/fchmodat.c
index c41ebb290d..ac318ceb79 100644
--- a/sysdeps/unix/sysv/linux/fchmodat.c
+++ b/sysdeps/unix/sysv/linux/fchmodat.c
@@ -18,24 +18,65 @@
#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)
+ {
+ if (errno == ENFILE || errno == EMFILE)
+ /* These errors cannot happen with a straight fchmodat
+ operation because it does not create file descriptors,
+ so hide them. */
+ __set_errno (EOPNOTSUPP);
+ /* Otherwise, this should accurately reflect the expected
+ error from fchmodat (e.g., EBADF or ENOENT). */
+ return pathfd;
+ }
+
+ char buf[32];
+ if (__snprintf (buf, sizeof (buf), "/proc/self/fd/%d", pathfd) < 0)
+ {
+ __close_nocancel (pathfd);
+ return INLINE_SYSCALL_ERROR_RETURN_VALUE (EOPNOTSUPP);
+ }
- 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. In general, we cannot use
+ openat with AT_EMPTY_PATH to upgrade the descriptor
+ because we may not have permission to open the file,
+ and opening files and closing them again may have side
+ effects (such as rewinding tape devices, or releasing
+ POSIX locks). */
+ __set_errno (EOPNOTSUPP);
+ }
+ __close_nocancel (pathfd);
+ return ret;
+ }
}
libc_hidden_def (fchmodat)
--
2.24.1