Linux 3.10 (and many earlier kernels) allow flink using an incantation like linkat(AT_FDCWD, "/proc/self/fd/N", destdirfd, newname, AT_SYMLINK_FOLLOW); It's possible to do much the same thing using linkat(oldfd, "", destdirfd, newname, AT_EMPTY_PATH) if you're privileged on 3.10, and the requirement for privilege is dropped in 3.11-rc5. The immediate motivation for dropping the privilege requirement is the O_TMPFILE changes: you can create a temporary file with O_TMPFILE, write to it, and then give it a name with linkat(..., AT_EMPTY_PATH). You can prevent this behavior by using O_TMPFILE | O_EXCL. Apparently there's some kind of new security issue here [1], but I don't know what it is. So I'd like to get other people's thoughts. Some notes: All linkat variations do this: /* Make sure we don't allow creating hardlink to an unlinked file */ if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE)) error = -ENOENT; That means that deleted files (except for O_TMPFILE, which sets I_LINKABLE) can't be flinked. Both flink variants work on O_PATH fds. I've attached some test code if you want to play with this stuff. Possible changes include inspecting f_cred before flink, requiring I_ILINKABLE if unprivileged, and reverting the 3.11 change. [1] https://lwn.net/Articles/562488/ -- see the comments
#include <stdio.h> #include <err.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define __O_TMPFILE 020000000 #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) #define AT_EMPTY_PATH 0x1000 int main(int argc, char **argv) { char buf[128]; if (argc != 4) errx(1, "Usage: flinktest TMPDIR PATH linkat|proc"); int fd = open(argv[1], O_TMPFILE | O_RDWR, 010600); if (fd == -1) err(1, "O_TMPFILE"); write(fd, "test", 4); if (!strcmp(argv[3], "linkat")) { if (linkat(fd, "", AT_FDCWD, argv[2], AT_EMPTY_PATH) != 0) err(1, "linkat"); } else if (!strcmp(argv[3], "proc")) { sprintf(buf, "/proc/self/fd/%d", fd); if (linkat(AT_FDCWD, buf, AT_FDCWD, argv[2], AT_SYMLINK_FOLLOW) != 0) err(1, "linkat"); } else { errx(1, "invalid mode"); } return 0; }
#include <stdio.h> #include <err.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define AT_EMPTY_PATH 0x1000 #ifndef O_PATH #define O_PATH 010000000 #endif int main(int argc, char **argv) { char buf[128]; if (argc != 5) errx(1, "Usage: flinktest OLDPATH NEWPATH <normal|O_PATH> AT_EMPTY_PATH|proc"); int flag; if (!strcmp(argv[3], "normal")) flag = O_RDONLY; else if (!strcmp(argv[3], "O_PATH")) flag = O_PATH; else errx(1, "bad open mode"); int fd = open(argv[1], flag); if (fd == -1) err(1, "open"); if (!strcmp(argv[4], "AT_EMPTY_PATH")) { if (linkat(fd, "", AT_FDCWD, argv[2], AT_EMPTY_PATH) != 0) err(1, "linkat"); } else if (!strcmp(argv[4], "proc")) { sprintf(buf, "/proc/self/fd/%d", fd); if (linkat(AT_FDCWD, buf, AT_FDCWD, argv[2], AT_SYMLINK_FOLLOW) != 0) err(1, "linkat"); } else { errx(1, "invalid mode"); } return 0; }