فهرست منبع

work around linux's lack of flags argument to fchmodat syscall

previously, the AT_SYMLINK_NOFOLLOW flag was ignored, giving
dangerously incorrect behavior -- the target of the symlink had its
modes changed to the modes (usually 0777) intended for the symlink).
this issue was amplified by the fact that musl provides lchmod, as a
wrapper for fchmodat, which some archival programs take as a sign that
symlink modes are supported and thus attempt to use.

emulating AT_SYMLINK_NOFOLLOW was a difficult problem, and I
originally believed it could not be solved, at least not without
depending on kernels newer than 3.5.x or so where O_PATH works halfway
well. however, it turns out that accessing O_PATH file descriptors via
their pseudo-symlink entries in /proc/self/fd works much better than
trying to use the fd directly, and works even on older kernels.
moreover, the kernel has permanently pegged these references to the
inode obtained by the O_PATH open, so there should not be race
conditions with the file being moved, deleted, replaced, etc.
Rich Felker 11 سال پیش
والد
کامیت
0dc4824479
1فایلهای تغییر یافته به همراه29 افزوده شده و 1 حذف شده
  1. 29 1
      src/stat/fchmodat.c

+ 29 - 1
src/stat/fchmodat.c

@@ -1,7 +1,35 @@
 #include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
 #include "syscall.h"
 
 int fchmodat(int fd, const char *path, mode_t mode, int flag)
 {
-	return syscall(SYS_fchmodat, fd, path, mode, flag);
+	if (!flag) return syscall(SYS_fchmodat, fd, path, mode, flag);
+
+	if (flag != AT_SYMLINK_NOFOLLOW)
+		return __syscall_ret(-EINVAL);
+
+	struct stat st;
+	int ret, fd2;
+	char proc[15+3*sizeof(int)];
+
+	if ((ret = __syscall(SYS_fstatat, fd, path, &st, flag)))
+		return __syscall_ret(ret);
+	if (S_ISLNK(st.st_mode))
+		return __syscall_ret(-EOPNOTSUPP);
+
+	if ((fd2 = __syscall(SYS_openat, fd, path, O_RDONLY|O_PATH|O_NOFOLLOW|O_NOCTTY)) < 0) {
+		if (fd2 == -ELOOP)
+			return __syscall_ret(-EOPNOTSUPP);
+		return __syscall_ret(fd2);
+	}
+
+	snprintf(proc, sizeof proc, "/proc/self/fd/%d", fd2);
+	if (!(ret = __syscall(SYS_stat, proc, &st)) && !S_ISLNK(st.st_mode))
+		ret = __syscall(SYS_chmod, proc, mode);
+
+	__syscall(SYS_close, fd2);
+	return __syscall_ret(ret);
 }