From 0653109a3075c87b5d176d4be3f96e75968a5fc4 Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:41:56 +0900 Subject: [PATCH] coreutils: dummy -> true -> coreutils symlink chain runs as true --- docs/src/extensions.md | 5 ++++- src/bin/coreutils.rs | 12 ++++++++++++ src/common/validation.rs | 8 +++++--- tests/test_util_name.rs | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/src/extensions.md b/docs/src/extensions.md index bb0dfff0617..e01fc502477 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -25,12 +25,15 @@ $ ls -w=80 With GNU coreutils, `--help` usually prints the help message and `--version` prints the version. We also commonly provide short options: `-h` for help and `-V` for version. -## `coreutils` +## `coreutils` (multi-call binary) Our `coreutils` calls utility by `coreutils utility-name` and has `--list` to run against busybox test suite. Our `coreutils` is called as `utility-name` if its binary name ends with `utility-name` to support prefixed names. Longer name is prioritized e.g. `sum` with the prefix `ck` is called as `cksum`. +On Linux, the symlink chain `dummy` -> `utility-name` -> `coreutils` tries to run `utility-name` +i.e. `dummy` tries to behave like individual binaries. It is important if `utility-name` is `true`. + ## `env` GNU `env` allows the empty string to be used as an environment variable name. diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 84c7ff0ab85..391ce9af9db 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -73,6 +73,18 @@ fn main() { // todo: Remove support of "*box" from binary uucore::set_utility_is_second_arg(); args.next() + // with the invalid_name -> true -> coreutils symlink chain, run true + // this is possible on the platform not using argv0 + } else if cfg!(any(target_os = "linux", target_os = "android")) + && let Ok(binary) = std::fs::read_link(&binary) + && let Some(valid) = validation::name(&binary) + && !valid.ends_with("utils") + && let Some(&matched) = utils + .keys() + .filter(|&&u| valid.ends_with(u)) + .max_by_key(|u| u.len()) + { + Some(OsString::from(matched)) } else { validation::not_found(&OsString::from(binary_as_util)); }; diff --git a/src/common/validation.rs b/src/common/validation.rs index ad55165df43..360fd307772 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -94,15 +94,17 @@ pub fn binary_path(args: &mut impl Iterator) -> PathBuf { let exec_path = Path::new(OsStr::from_bytes(execfn_bytes)); let argv0 = args.next().unwrap(); let mut shebang_buf = [0u8; 2]; - // exec_path is wrong when called from shebang or memfd_create (/proc/self/fd/*) - // argv0 is not full-path when called from PATH if execfn_bytes.rsplit(|&b| b == b'/').next() == argv0.as_bytes().rsplit(|&b| b == b'/').next() - || execfn_bytes.starts_with(b"/proc/") + { + // argv0 is not full-path when called from PATH + exec_path.into() + } else if execfn_bytes.starts_with(b"/proc/") || (File::open(Path::new(exec_path)) .and_then(|mut f| f.read_exact(&mut shebang_buf)) .is_ok() && &shebang_buf == b"#!") { + // exec_path is wrong when called from shebang or memfd_create (/proc/self/fd/*) argv0.into() } else { exec_path.into() diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index 53c3ef55334..38d9ca8f7d8 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -26,6 +26,22 @@ fn init() { eprintln!("Setting UUTESTS_BINARY_PATH={TESTS_BINARY}"); } +#[test] +#[cfg(all(feature = "env", any(target_os = "linux", target_os = "android")))] +fn binary_name_symlink_chain() { + let ts = TestScenario::new("chain"); + let core_path = &ts.bin_path; + let env_path = ts.fixtures.plus("prefixed-env"); + let dummy_path = ts.fixtures.plus("dummy"); + symlink_file(core_path, &env_path).unwrap(); + symlink_file(&env_path, &dummy_path).unwrap(); + let is_env = std::process::Command::new(&dummy_path) + .status() + .unwrap() + .success(); + assert!(is_env, "symlink chain dummy -> env -> coreutils failed"); +} + #[test] #[cfg(all(feature = "env", any(target_os = "linux", target_os = "android")))] fn binary_name_protection() {