copy: avoid following fifo/node chmod target

Use AT_SYMLINK_NOFOLLOW when applying copied FIFO and device
node modes, matching the adjacent ownership and timestamp updates.

Follow-up for e69cc9eb36
This commit is contained in:
Luca Boccassi
2026-07-09 11:35:50 +01:00
parent f5e35f72cf
commit ea44ee4e8a
2 changed files with 66 additions and 2 deletions

View File

@@ -959,7 +959,7 @@ static int fd_copy_fifo(
AT_SYMLINK_NOFOLLOW) < 0)
r = -errno;
if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0)
if (fchmodat(dt, to, st->st_mode & 07777, AT_SYMLINK_NOFOLLOW) < 0)
r = -errno;
(void) utimensat(dt, to, (struct timespec[]) { st->st_atim, st->st_mtim }, AT_SYMLINK_NOFOLLOW);
@@ -1012,7 +1012,7 @@ static int fd_copy_node(
AT_SYMLINK_NOFOLLOW) < 0)
r = -errno;
if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0)
if (fchmodat(dt, to, st->st_mode & 07777, AT_SYMLINK_NOFOLLOW) < 0)
r = -errno;
(void) utimensat(dt, to, (struct timespec[]) { st->st_atim, st->st_mtim }, AT_SYMLINK_NOFOLLOW);

View File

@@ -3,6 +3,7 @@
#include <linux/fsverity.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
@@ -308,6 +309,69 @@ TEST(copy_tree_at_symlink) {
fd = safe_close(fd);
}
TEST(copy_tree_fifo_chmod_symlink_race) {
_cleanup_(rm_rf_physical_and_freep) char *srcp = NULL, *dstp = NULL;
_cleanup_close_ int src = -EBADF, dst = -EBADF;
_cleanup_close_pair_ int ready[2] = EBADF_PAIR, stop[2] = EBADF_PAIR;
struct stat st;
int status;
pid_t pid;
char c;
if (!slow_tests_enabled())
return (void) log_tests_skipped("slow tests are disabled");
ASSERT_OK(src = mkdtemp_open(NULL, 0, &srcp));
ASSERT_OK(dst = mkdtemp_open(NULL, 0, &dstp));
ASSERT_OK_ERRNO(mkfifoat(src, "fifo", 0777));
ASSERT_OK_ERRNO(fchmodat(src, "fifo", 0777, 0));
ASSERT_OK(write_string_file_at(dst, "victim", "victim", WRITE_STRING_FILE_CREATE));
ASSERT_OK_ERRNO(fchmodat(dst, "victim", 0600, 0));
ASSERT_OK_ERRNO(pipe2(ready, O_CLOEXEC));
ASSERT_OK_ERRNO(pipe2(stop, O_CLOEXEC|O_NONBLOCK));
pid = ASSERT_OK_ERRNO(fork());
if (pid == 0) {
ready[0] = safe_close(ready[0]);
stop[1] = safe_close(stop[1]);
assert_se(write(ready[1], &(const char) { 'x' }, 1) == 1);
for (;;) {
char stop_char;
ssize_t n = read(stop[0], &stop_char, 1);
if (n >= 0)
_exit(EXIT_SUCCESS);
(void) unlinkat(dst, "fifo", 0);
(void) symlinkat("victim", dst, "fifo");
}
}
ready[1] = safe_close(ready[1]);
stop[0] = safe_close(stop[0]);
ASSERT_OK_EQ_ERRNO(read(ready[0], &c, 1), 1);
bool changed = false;
for (unsigned i = 0; i < 20000; i++) {
(void) copy_tree_at(src, "fifo", dst, "fifo", UID_INVALID, GID_INVALID, COPY_REPLACE, NULL, NULL);
ASSERT_OK_ERRNO(fstatat(dst, "victim", &st, 0));
if ((st.st_mode & 0777) != 0600) {
changed = true;
break;
}
}
ASSERT_OK_EQ_ERRNO(write(stop[1], &(const char) { 'x' }, 1), 1);
ASSERT_OK_ERRNO(waitpid(pid, &status, 0));
ASSERT_TRUE(WIFEXITED(status));
ASSERT_FALSE(changed);
}
TEST_RET(copy_bytes) {
_cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
_cleanup_close_ int infd = -EBADF;