bug#79433: 9.7: cp(1): lchownat(2/3) fallback bogus?

Steffen Nurpmeso <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Message-ID <20250916004007.NC_8Vftj@steffen%sdaoden.eu>
Hello.

Sorry for the late reply.

Paul Eggert wrote in
 <[email protected]>:
 |On 2025-09-13 10:36, Steffen Nurpmeso wrote:
 |> Paul Eggert wrote in
 |>|that cp fails because fchmodat2 fails. So it sounds like we have been
 |>|barking up the wrong tree by worrying about the chown-related syscalls,
 |>|and that we should instead worry about chmod-related syscalls.
 |> 
 |> Seems like that.
 |
 |On further looking at your trace in 
 |<https://debbugs.gnu.org/cgi/bugreport.cgi?bug=79433#23> it seems that I 
 |was mistaken. Even though the fchmodat2 syscall fails with ENOSYS, it 

Please stop right here.
I followed your advice and rebuilt coreutils 9.7.  I cannot
reproduce the error, strace(1)d flows change due to fchmodat2().
I give the logs below.

  $ grep chown /usr/include/asm/unistd_64.h
  #define __NR_chown 92
  #define __NR_fchown 93
  #define __NR_lchown 94
  #define __NR_fchownat 260

  $ grep fchmod /usr/include/asm/unistd_64.h
  #define __NR_fchmod 91
  #define __NR_fchmodat 268

  $ ll /bin/cp
  -rwxr-xr-x 1 root root 126240 Apr 29 11:59 /bin/cp*

  $ ll src/cp
  -rwxr-x--- 1 steffen steffen 480888 Sep 16 01:18 src/cp*

On my box, since April, quite some things changed (CRUX is rolling
release, except for certain core ports which only get bugfixes).
I cannot reconstruct exact software environment from April.

  $ strings /bin/cp | grep -i gcc:
  GCC: (CRUX-x86_64-multilib) 14.2.0

  $ strings src/cp | grep -i gcc:
  GCC: (CRUX-x86_64-multilib) 14.3.0

That.  glibc 2.40 is here since last Octobre (20th) i would
think, it was updated three or four times (CVEs .. at least).
Kernel is 6.1.* ever since it was declared LTS.

 |appears that the glibc fchmodat library function then calls openat with 
 |O_NOFOLLOW|O_PATH and then calls fstatat+AT_EMPTY_PATH, determines that 
 |"c" is a symlink, and then returns -1 with errno=ENOTSUP.
 |
 |But then I'm lost. If b is a symlink, I don't see why 'cp -a b c' would 
 |ever call fchmodat. The coreutils 9.7 source code says that fopr 
 |symlinks copy_internal should call symlinkat via force_symlinkat 
 |(copy.c:3117), then fchownat via lchownat (copy.c:3152), then utimensat 
 |(copy.c:3227), but it should never call fchmodat.
 |
 |Could you investigate why coreutils 9.7 cp is not behaving the way the 
 |source code says it should? You can use a debugger, or insert print 
 |statements, to figure out why in this particular case it is going awry.
 |
 |Lets get the chmod issue figured out first. We can worry about the chown 
 |issue later; one thing at a time.

So i instrumented the code paths first, which was stupid, since it
just works if compiled now.  I do have the /bin/cp from April. 
Here is the diff.

  --- _bin_cp.log	2025-09-16 01:22:35.366585945 +0200
  +++ _src_cp.log	2025-09-16 01:23:14.693251872 +0200
  @@ -1,40 +1,40 @@
  -execve("/bin/cp", ["/bin/cp", "-a", "xb", "xc"], 0x7ffee5e2ffb8 /* 94 vars */) = 0
  -brk(NULL)                               = 0x55d3bf6d7000
  -mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbbd5a6c000
  +execve("src/cp", ["src/cp", "-a", "xb", "xc"], 0x7ffcd92e2298 /* 94 vars */) = 0
  +brk(NULL)                               = 0x55b6475d1000
  +mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3595bd8000
   access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
   ...
   openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
  @@ -43,52 +43,106 @@ read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0
  ...
   prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
  -getrandom("\x03\x0b\xe6\x00\xb2\xbe\xbd\x51", 8, GRND_NONBLOCK) = 8
  -brk(NULL)                               = 0x55d3bf6d7000
  -brk(0x55d3bf6f8000)                     = 0x55d3bf6f8000
  +getrandom("\x4f\x4b\xf5\x75\x90\xf4\x18\xef", 8, GRND_NONBLOCK) = 8
  +brk(NULL)                               = 0x55b6475d1000
  +brk(0x55b6475f2000)                     = 0x55b6475f2000
   openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
   fstat(3, {st_mode=S_IFREG|0644, st_size=3061520, ...}) = 0
  -mmap(NULL, 3061520, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fbbd5400000
  +mmap(NULL, 3061520, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f3595600000
   close(3)                                = 0
   geteuid()                               = 1000
  +write(2, "PRE DO_COPY\n", 12PRE DO_COPY
  +)           = 12
  +write(2, "DO_COPY\n", 8DO_COPY
  +)                = 8
   openat(AT_FDCWD, "xc", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOTDIR (Not a directory)
  +write(2, "WORK2 xb xc\n", 12WORK2 xb xc
  +)           = 12
  +write(2, "COPY_INTERNAL xb -> xc\n", 23COPY_INTERNAL xb -> xc
  +) = 23
   newfstatat(AT_FDCWD, "xb", {st_mode=S_IFLNK|0777, st_size=2, ...}, AT_SYMLINK_NOFOLLOW) = 0
  +write(2, "      10\n", 9      10
  +)               = 9
   newfstatat(AT_FDCWD, "xc", {st_mode=S_IFLNK|0777, st_size=2, ...}, AT_SYMLINK_NOFOLLOW) = 0
   unlinkat(AT_FDCWD, "xc", 0)             = 0
   readlink("xb", "xa", 3)                 = 2
   symlinkat("xa", AT_FDCWD, "xc")         = 0

==DIVERT

  -newfstatat(AT_FDCWD, "xc", {st_mode=S_IFLNK|0777, st_size=2, ...}, AT_SYMLINK_NOFOLLOW) = 0
  -lchown("xc", 1000, 1000)                = 0
  -fchmodat2(AT_FDCWD, "xc", 0777, AT_SYMLINK_NOFOLLOW) = -1 ENOSYS (Function not implemented)
  -openat(AT_FDCWD, "xc", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 3
  -newfstatat(3, "", {st_mode=S_IFLNK|0777, st_size=2, ...}, AT_EMPTY_PATH) = 0
  -close(3)                                = 0

Like you say -- sysdeps/unix/sysv/linux/fchmodat.c went over
fchmodat_fallback().  How did it get here at all is your question;
well, /bin/cp is stripped, CRUX strips stuff.

  -fcntl(1, F_GETFL)                       = 0x2 (flags O_RDWR)
  -write(2, "cp: ", 4cp: )                     = 4
  -write(2, "failed to preserve ownership for"..., 35failed to preserve ownership for xc) = 35
  -write(2, ": Operation not supported", 25: Operation not supported) = 25
  -write(2, "\n", 1
  -)                       = 1
  +write(2, "      314\n", 10      314
  +)             = 10
...
  +write(2, "      316\n", 10      316
  +)             = 10
  +fchownat(AT_FDCWD, "xc", 1000, 1000, AT_SYMLINK_NOFOLLOW) = 0
  +write(2, "      320\n", 10      320
  +)             = 10
..
  +utimensat(AT_FDCWD, "xc", [{tv_sec=1757977042, tv_nsec=253998375} /* 2025-09-16T00:57:22.253998375+0200 */, {tv_sec=1757977033, tv_nsec=957331866} /* 2025-09-16T00:57:13.957331866+0200 */], AT_SYMLINK_NOFOLLOW) = 0
  +llistxattr("xb", NULL, 0)               = 0
  +llistxattr("xb", 0x7ffdb169b050, 0)     = 0
  +write(2, "do_copy returns 1\n", 18do_copy returns 1
  +)     = 18
   lseek(0, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
   close(0)                                = 0
   close(1)                                = 0
   close(2)                                = 0
  -exit_group(1)                           = ?
  -+++ exited with 1 +++
  +exit_group(0)                           = ?
  ++++ exited with 0 +++

I do have that /bin/cp.
Btw i note that if i strip src/cp it is much smaller than /bin/cp,
but, different compiler etc.
Well, that will be hard, to get more out of that.  I do not use
debuggers.  Well, i guess i could install gdb and set breakpoint
around symlinkat(), then step and try to look around.

 --End of <[email protected]>

How likely is a miscompilation of gcc 4.2.0?

--steffen
|
|Der Kragenbaer,                The moon bear,
|der holt sich munter           he cheerfully and one by one
|einen nach dem anderen runter  wa.ks himself off
|(By Robert Gernhardt)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.