uutils / uutils/coreutils

bug(cp): race condition possible when --backup= or -b and after bypassing the same file check, it leads to stat the same file twice

Open
#13,104 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

U - cp
Dominant language
Rust
Stars
24.1k
Forks
2k
Avg merge
1d 5h
Merged PRs (30d)
365

Description

Hi, uutils mainteners

possibly there was a race condition when

relunsec@relunsec:~/software/coreutils/target/debug$ strace ./cp --backup=nil ./a a
execve("./cp", ["./cp", "--backup=nil", "./a", "a"], 0x7ffe2b1d81f8 /* 74 vars */) = 0
brk(NULL)                               = 0x5f65dabe7000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7dd457ae5000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=97721, ...}) = 0
mmap(NULL, 97721, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7dd457acd000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=187120, ...}) = 0
mmap(NULL, 185256, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7dd457a9f000
mmap(0x7dd457aa3000, 147456, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = 0x7dd457aa3000
mmap(0x7dd457ac7000, 16384, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7dd457ac7000
mmap(0x7dd457acb000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2c000) = 0x7dd457acb000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=1198376, ...}) = 0
mmap(NULL, 1200152, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7dd457979000
mmap(0x7dd45798b000, 655360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12000) = 0x7dd45798b000
mmap(0x7dd457a2b000, 466944, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xb2000) = 0x7dd457a2b000
mmap(0x7dd457a9d000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x123000) = 0x7dd457a9d000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \250\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 840, 64) = 840
fstat(3, {st_mode=S_IFREG|0755, st_size=2186512, ...}) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 840, 64) = 840
mmap(NULL, 2231696, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7dd457758000
mmap(0x7dd457780000, 1671168, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7dd457780000
mmap(0x7dd457918000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7dd457918000
mmap(0x7dd457966000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x20d000) = 0x7dd457966000
mmap(0x7dd45796c000, 52624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7dd45796c000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7dd457756000
arch_prctl(ARCH_SET_FS, 0x7dd457756d80) = 0
set_tid_address(0x7dd4577573a8)         = 117069
set_robust_list(0x7dd457757060, 24)     = 0
rseq(0x7dd457756ac0, 0x21, 0, 0x53053053) = 0
mprotect(0x7dd457966000, 16384, PROT_READ) = 0
mprotect(0x7dd457a9d000, 4096, PROT_READ) = 0
mprotect(0x7dd457acb000, 4096, PROT_READ) = 0
mprotect(0x5f65b4f8b000, 196608, PROT_READ) = 0
mprotect(0x7dd457b2a000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
getrandom("\x81\x98\xb5\x1c\x50\x55\x94\xb4", 8, GRND_NONBLOCK) = 8
munmap(0x7dd457acd000, 97721)           = 0
fcntl(0, F_GETFD)                       = 0
fcntl(1, F_GETFD)                       = 0
fcntl(2, F_GETFD)                       = 0
rt_sigaction(SIGPIPE, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
poll([{fd=0, events=0}, {fd=1, events=0}, {fd=2, events=0}], 3, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7dd45779dcb0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
brk(NULL)                               = 0x5f65dabe7000
brk(0x5f65dac08000)                     = 0x5f65dac08000
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "5f65b4b39000-5f65b4cd7000 r--p 0"..., 1024) = 1024
read(3, "--p 0020d000 00:1e 447426       "..., 1024) = 1024
read(3, "7dd457acb000 r--p 00028000 00:1e"..., 1024) = 1024
read(3, ":1e 447420                     /"..., 1024) = 412
lseek(3, 0, SEEK_CUR)                   = 3484
lseek(3, 3400, SEEK_SET)                = 3400
close(3)                                = 0
sched_getaffinity(117069, 32, [0 1 2 3 4 5 6 7]) = 8
rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7dd457ae2000
mprotect(0x7dd457ae2000, 4096, PROT_NONE) = 0
sigaltstack({ss_sp=0x7dd457ae3000, ss_flags=0, ss_size=8192}, NULL) = 0
gettid()                                = 117069
rt_sigaction(SIGSEGV, {sa_handler=0x5f65b4f69380, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7dd45779dcb0}, NULL, 8) = 0
rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x5f65b4f69380, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7dd45779dcb0}, NULL, 8) = 0
statx(AT_FDCWD, "/home/relunsec/software/coreutils/src/uucore/../uu/cp/locales", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFDIR|0775, stx_size=4, ...}) = 0
readlink("/home", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
readlink("/home/relunsec", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
readlink("/home/relunsec/software", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
readlink("/home/relunsec/software/coreutils", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
readlink("/home/relunsec/software/coreutils/src", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
readlink("/home/relunsec/software/coreutils/src/uucore", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
faccessat2(AT_FDCWD, "/home/relunsec/software/coreutils/src/uucore/", F_OK, AT_EACCESS) = 0
readlink("/home/relunsec/software/coreutils/src/uu", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
readlink("/home/relunsec/software/coreutils/src/uu/cp", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
readlink("/home/relunsec/software/coreutils/src/uu/cp/locales", 0x7ffd1b58a6c0, 1023) = -1 EINVAL (Invalid argument)
statx(AT_FDCWD, "/home/relunsec/software/coreutils/src/uucore/locales", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFDIR|0775, stx_size=4, ...}) = 0
openat(AT_FDCWD, "/home/relunsec/software/coreutils/src/uucore/locales/en-US.ftl", O_RDONLY|O_CLOEXEC) = 3
statx(3, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=4113, ...}) = 0
read(3, "# Common strings shared across a"..., 4113) = 4113
read(3, "", 32)                         = 0
close(3)                                = 0
statx(AT_FDCWD, "/home/relunsec/software/coreutils/src/uucore/../uu/cp/locales", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFDIR|0775, stx_size=4, ...}) = 0
openat(AT_FDCWD, "/home/relunsec/software/coreutils/src/uucore/../uu/cp/locales/en-US.ftl", O_RDONLY|O_CLOEXEC) = 3
statx(3, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=8247, ...}) = 0
read(3, "cp-about = Copy SOURCE to DEST, "..., 8247) = 8247
read(3, "", 32)                         = 0
close(3)                                = 0
brk(0x5f65dac2a000)                     = 0x5f65dac2a000
rt_sigaction(SIGPIPE, {sa_handler=SIG_DFL, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7dd45779dcb0}, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7dd45779dcb0}, 8) = 0
rt_sigaction(SIGSEGV, {sa_handler=SIG_DFL, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7dd45779dcb0}, {sa_handler=0x5f65b4f69380, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7dd45779dcb0}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=SIG_DFL, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7dd45779dcb0}, {sa_handler=0x5f65b4f69380, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7dd45779dcb0}, 8) = 0
ioctl(1, TCGETS2, {c_iflag=BRKINT|IGNPAR|ICRNL|IXON|IMAXBEL, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
rt_sigprocmask(SIG_BLOCK, ~[], [], 8)   = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_DROPPABLE|MAP_ANONYMOUS, -1, 0) = 0x7dd457ae1000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7dd457ae0000
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
getrandom("\x85\x77\x13\xb0\x90\x41\x62\x17\xa8\xc4\x64\x8a\x42\x01\x72\x87\xd5\x12\xfb\x5b\x91\x5a\x4a\xd9\xdc\xb7\xab\x21\xf3\xe5\xdc\x96", 32, 0) = 32
newfstatat(AT_FDCWD, "a", {st_mode=S_IFREG|0664, st_size=6, ...}, 0) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "./a", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "./a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "./a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
newfstatat(AT_FDCWD, "./a", {st_mode=S_IFREG|0664, st_size=6, ...}, 0) = 0
newfstatat(AT_FDCWD, "a", {st_mode=S_IFREG|0664, st_size=6, ...}, 0) = 0
statx(AT_FDCWD, "./a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
newfstatat(AT_FDCWD, "./a", {st_mode=S_IFREG|0664, st_size=6, ...}, 0) = 0
newfstatat(AT_FDCWD, "a", {st_mode=S_IFREG|0664, st_size=6, ...}, 0) = 0
statx(AT_FDCWD, "a.~1~", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, 0x7ffd1b588060) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "./a", {st_mode=S_IFREG|0664, st_size=6, ...}, 0) = 0
newfstatat(AT_FDCWD, "a~", {st_mode=S_IFREG|0664, st_size=6, ...}, 0) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
openat(AT_FDCWD, "a", O_RDONLY|O_CLOEXEC) = 3
statx(3, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
openat(AT_FDCWD, "a~", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0100664) = 4
statx(4, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=0, ...}) = 0
fchmod(4, 0100664)                      = 0
copy_file_range(3, NULL, 4, NULL, 1073741824, 0) = 6
copy_file_range(3, NULL, 4, NULL, 1073741824, 0) = 0
close(4)                                = 0
close(3)                                = 0
statx(AT_FDCWD, "./a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
statx(AT_FDCWD, "a", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=6, ...}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=8192}, NULL) = 0
munmap(0x7dd457ae2000, 12288)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++

it calls statx syscall twice, against the same file which leaves a massive ms of race condition possibility, because statx multiple times against the same file to a be swapped, the gnu one

relunsec@relunsec:~/software/coreutils/target/debug$ gnucp --backup=nil ./a a
gnucp: './a' and 'a' are the same file

correctly rejects that

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the reported cp --backup=nil ./a a command with strace. Then inspect the cp handling for --backup or -b around the repeated stat calls for a and ./a. Done means the suspected race is confirmed and the issue has a concrete, testable change target.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.