You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
chmod -R changes entries below its command-line argument with a raw syscall(SYS_fchmodat2, ...) instead of a libc call, so LD_PRELOAD tools (fakeroot, fakechroot, pseudo) never see it. The mode changes on disk, but their bookkeeping does not, so later stat() calls report the old mode. Exit status is 0 and stderr is empty, so the loss is silent
This can be reproduced by running the following commands using uutils 0.10.0, fakeroot 1.37.1.2, Linux 6.16:
mkdir -p d/sub
fakeroot sh -c 'chown -R daemon:daemon d && chmod -R u+s d && find d -printf "%M %p\n" | sort'
GNU coreutils:
drwsrwxr-x d
drwsrwxr-x d/sub
uutils:
drwsrwxr-x d
drwxrwxr-x d/sub # setuid bit lost
The cause, as I have found, is that DirFd::chmod_at in src/uucore/src/lib/features/safe_traversal.rs calls libc::syscall(452, ...) directly. The command-line argument itself is unaffected because it goes through fs::set_permissions -> chmod()
The raw syscall is #[cfg]-gated to asm-generic arches (x86_64, x86, arm, aarch64, riscv), so it does not reproduce on e.g. ppc64el or s390x, which take the fchmodat fallback. Introduced by #11918
Same class as #13399, different call site (that one is rustix in id, this one is a hand-written libc::syscall in uucore)
Please fill bug report to tools using LD_PRELOAD. It is actually their bug breaking static musl binaries.
I understand the point but please let me first elaborate on why I decided to open the bug specifically here
fakeroot hooks fchmodat(), which is the libc function for this. GNU coreutils calls it, and it all works.
glibc doesn't export fchmodat2 as a symbol at all. It's an internal detail of how fchmodat() is implemented on new kernels, not a libc function in its own right. So there's nothing there for fakeroot to hook, and asking LD_PRELOAD tools to fix this means asking them to intercept a raw syscall number, per arch, because one program went around the API
And the bypass gains nothing, as fchmodat(fd, name, mode, AT_SYMLINK_NOFOLLOW) on glibc issues the exact same fchmodat2 syscall. GNU chmod -R and uutils chmod -R produce identical straces. The only difference is that GNU goes through the symbol, and uutils doesn't
(chmod_at uses libc::syscall, so it's already in libc, and static musl can't interpose that any more than fchmodat)
Moreover, the fix on the uutils side would be small, as it can call fchmodat() first and fall back to the raw syscall only on EOPNOTSUPP/ENOSYS. musl behaviour stays unchanged
The impact is that this fails silently. The chmod succeeds, the exit code is 0, nothing on stderr, so the tool just never sees the change and the resulting file permissions are wrong
Imagine what happen if glibc added support for new syscall for some reason e.g. security before fixing fakeroot. It is new version of fakeroot's role if new syscall does same things. Rust cannot guarantee matching all calls with C. (It would be nightmare if we re-write everything in C (libc crate) and remove std to support LD_PRELOAD'ing all functions.)
"Workaround is small" does not mean it is clean in the code base and making a sense.
Imagine what happen if glibc added support for new syscall for some reason e.g. security before fixing fakeroot. It is new version of fakeroot's role if new syscall does same things. Rust cannot guarantee matching all calls with C. (It would be nightmare if we re-write everything in C (libc crate) and remove std to support LD_PRELOAD'ing all functions.)
I've done some research, and actually that case already happened, and it went the other way. glibc moved fchmodat() onto the new syscall (in commit 65341f7bbea824d2ff9d37db15d8be162df42bd3)
"linux: Use fchmodat2 on fchmod for flags different than 0 (BZ 26401)" (in sysdeps/unix/sysv/linux/fchmodat.c):
fakeroot needed no change for that, because it hooks the symbol, not the syscall. So that scenario argues for actually going through libc, not against it :)
It's also the same fchmodat2-first-then-ENOSYS-fallback that chmod_at implements by hand as glibc already does it behind fchmodat()
fakeroot needed no change for that, because it hooks the symbol, not the syscall. So that scenario argues for actually going through libc, not against it :)
No. fakeroot is using a wrong tool: LD_PRELOAD. fakeroot itself should be updated to use ptrace in any cases.
Rust can remove dependency on glibc from std in the future theorically.
@julian-klode Please send a fix to fakeroot's upstream to use ptrace or something else. Adding hack to support hack is not ideal and static binaries are still ignored by them.
Activity
oech3 commentedon Aug 19, 2026
Please fill bug report to tools using
LD_PRELOAD. It is actually their bug breaking static musl binaries.nadzyah commentedon Aug 20, 2026
I understand the point but please let me first elaborate on why I decided to open the bug specifically here
fakeroot hooks
fchmodat(), which is the libc function for this. GNU coreutils calls it, and it all works.glibc doesn't export
fchmodat2as a symbol at all. It's an internal detail of howfchmodat()is implemented on new kernels, not a libc function in its own right. So there's nothing there for fakeroot to hook, and askingLD_PRELOADtools to fix this means asking them to intercept a raw syscall number, per arch, because one program went around the APIAnd the bypass gains nothing, as
fchmodat(fd, name, mode, AT_SYMLINK_NOFOLLOW)on glibc issues the exact same fchmodat2 syscall. GNUchmod -Rand uutilschmod -Rproduce identical straces. The only difference is that GNU goes through the symbol, and uutils doesn't(
chmod_atuseslibc::syscall, so it's already in libc, and static musl can't interpose that any more thanfchmodat)Moreover, the fix on the uutils side would be small, as it can call
fchmodat()first and fall back to the raw syscall only onEOPNOTSUPP/ENOSYS. musl behaviour stays unchangedThe impact is that this fails silently. The chmod succeeds, the exit code is 0, nothing on stderr, so the tool just never sees the change and the resulting file permissions are wrong
oech3 commentedon Aug 20, 2026
nadzyah commentedon Aug 20, 2026
I've done some research, and actually that case already happened, and it went the other way. glibc moved fchmodat() onto the new syscall (in commit
65341f7bbea824d2ff9d37db15d8be162df42bd3)"linux: Use fchmodat2 on fchmod for flags different than 0 (BZ 26401)" (in sysdeps/unix/sysv/linux/fchmodat.c):
fakeroot needed no change for that, because it hooks the symbol, not the syscall. So that scenario argues for actually going through libc, not against it :)
It's also the same fchmodat2-first-then-ENOSYS-fallback that
chmod_atimplements by hand as glibc already does it behindfchmodat()And this isn't about rewriting in C or dropping std. std already calls libc here: https://github.com/rust-lang/rust/blob/f7d782a3be46d6bb4b9792fe69a61db389ba1769/library/std/src/sys/fs/unix.rs#L1865-L1867 which is why chmod's top-level path works under fakeroot and only
chmod_atdoesn'toech3 commentedon Aug 21, 2026
No.
fakerootis using a wrong tool:LD_PRELOAD.fakerootitself should be updated to use ptrace in any cases.Rust can remove dependency on glibc from std in the future theorically.
julian-klode commentedon Aug 24, 2026
As far as Ubuntu is concerned, this is a critical regression,
LD_PRELOADtools are an essential part of distributions.oech3 commentedon Aug 24, 2026
@julian-klode Please send a fix to
fakeroot's upstream to use ptrace or something else. Adding hack to support hack is not ideal and static binaries are still ignored by them.HackingRepo commentedon Aug 24, 2026
however
LD_PRELOADsupporting it is a massive security problem and also perfomance issue, fakeroot needs fix the issue and use proper waysylvestre commentedon Aug 24, 2026
I am evaluating a solution
chmod: use the libc fchmodat symbol for recursive mode changes
oech3 commentedon Aug 25, 2026
I still don't want to implicitly assume that Rust does not remove dependency on
glibc.We don't viorate anything about
chmod's role with our impl.chmod: use the libc fchmodat symbol for recursive mode changes
chmod: use the libc fchmodat symbol for recursive mode changes