Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .vscode/cspell.dictionaries/workspace.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ advapi32-sys
aho-corasick
backtrace
blake2b_simd
rustix

# * uutils project
uutils
Expand Down Expand Up @@ -360,6 +361,7 @@ uutests
uutils

# * function names
execfn
getcwd

# * other
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# coreutils (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information

# spell-checker:ignore (libs) bigdecimal datetime foldhash serde gethostid kqueue libselinux mangen memmap uuhelp startswith constness expl unnested logind cfgs interner
# spell-checker:ignore (libs) bigdecimal datetime foldhash serde gethostid kqueue libselinux mangen memmap uuhelp startswith constness expl unnested logind cfgs interner getauxval

[package]
name = "coreutils"
Expand Down Expand Up @@ -428,6 +428,9 @@ rlimit = "0.11.0"
rstest = "0.26.0"
rustc-hash = "2.1.1"
rust-ini = "0.21.0"
# binary name of coreutils can be hijacked by overriding getauxval via LD_PRELOAD
# So we use param and avoid libc backend
rustix = { version = "1.1.4", features = ["param"] }
Comment thread
oech3 marked this conversation as resolved.
same-file = "1.0.6"
self_cell = "1.0.4"
selinux = "=0.6.0"
Expand Down Expand Up @@ -595,6 +598,9 @@ who = { optional = true, version = "0.7.0", package = "uu_who", path = "src/uu/w
whoami = { optional = true, version = "0.7.0", package = "uu_whoami", path = "src/uu/whoami" }
yes = { optional = true, version = "0.7.0", package = "uu_yes", path = "src/uu/yes" }

[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
rustix.workspace = true

# this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)"
# factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" }

Expand Down
32 changes: 29 additions & 3 deletions src/common/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore prefixcat testcat
// spell-checker:ignore memfd_create prefixcat rsplit testcat

use std::ffi::{OsStr, OsString};
use std::io::{Write, stderr};
Expand Down Expand Up @@ -73,15 +73,41 @@ fn get_canonical_util_name(util_name: &str) -> &str {
}

/// Gets the binary path from command line arguments
/// # Panics
/// Panics if the binary path cannot be determined
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
match args.next() {
Some(ref s) if !s.is_empty() => PathBuf::from(s),
// the fallback is valid only for hardlinks
_ => std::env::current_exe().unwrap(),
}
}

/// Get actual binary path from kernel, not argv0, to prevent `env -a` from bypassing
/// AppArmor, SELinux policies on hard-linked binaries
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
use std::fs::File;
use std::io::Read;
use std::os::unix::ffi::OsStrExt;
let execfn = rustix::param::linux_execfn();
let execfn_bytes = execfn.to_bytes();
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/")
|| (File::open(Path::new(exec_path))
.and_then(|mut f| f.read_exact(&mut shebang_buf))
.is_ok()
&& &shebang_buf == b"#!")
{
argv0.into()
} else {
exec_path.into()
}
}
/// Extracts the binary name from a path
pub fn name(binary_path: &Path) -> Option<&str> {
binary_path.file_stem()?.to_str()
Expand Down
14 changes: 14 additions & 0 deletions tests/test_util_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ fn init() {
eprintln!("Setting UUTESTS_BINARY_PATH={TESTS_BINARY}");
}

#[test]
#[cfg(all(feature = "env", any(target_os = "linux", target_os = "android")))]
fn binary_name_protection() {
let ts = TestScenario::new("env");
let bin = ts.bin_path.clone();
ts.ucmd()
.arg("-a")
.arg("hijacked")
.arg(&bin)
.arg("--version")
.succeeds()
.stdout_contains("coreutils");
}

#[test]
#[cfg(feature = "ls")]
fn execution_phrase_double() {
Expand Down
Loading