Skip to content

Commit 48a6a42

Browse files
committed
coreutils: accept /dev/fd/ execfn prefixes
AT_EXECFN is /dev/fd/N if binary was called by fexecve(3), not /proc/self/fd/N
1 parent 6cb1d5e commit 48a6a42

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

src/common/validation.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore memfd_create prefixcat rsplit testcat
6+
// spell-checker:ignore execveat fexecve memfd_create prefixcat rsplit testcat
77

88
use std::ffi::{OsStr, OsString};
99
use std::io::{Write, stderr};
@@ -102,10 +102,12 @@ pub fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
102102
let exec_path = Path::new(OsStr::from_bytes(execfn_bytes));
103103
let argv0 = args.next().unwrap();
104104
let mut shebang_buf = [0u8; 2];
105-
// exec_path is wrong when called from shebang or memfd_create (/proc/self/fd/*)
105+
// exec_path is wrong when called from a shebang, or via fexecve/execveat:
106+
// the kernel reports /dev/fd/* and memfd_create/glibc's fallback /proc/self/fd/*
106107
// argv0 is not full-path when called from PATH
107108
if execfn_bytes.rsplit(|&b| b == b'/').next() == argv0.as_bytes().rsplit(|&b| b == b'/').next()
108109
|| execfn_bytes.starts_with(b"/proc/")
110+
|| execfn_bytes.starts_with(b"/dev/fd/")
109111
|| (File::open(Path::new(exec_path))
110112
.and_then(|mut f| f.read_exact(&mut shebang_buf))
111113
.is_ok()

tests/test_util_name.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,36 @@ fn binary_name_protection() {
4343
.stdout_contains("coreutils");
4444
}
4545

46+
// missing /proc/self/fd on Android?
47+
#[test]
48+
#[cfg(all(feature = "true", target_os = "linux", target_env = "gnu"))]
49+
fn binary_name_execfn_fallback() {
50+
use std::os::{fd::AsRawFd, unix::process::CommandExt};
51+
use std::process::Command;
52+
let ts = TestScenario::new("true");
53+
let fd = rustix::fs::memfd_create("a", rustix::fs::MemfdFlags::empty()).unwrap();
54+
let mut file = std::fs::File::from(fd);
55+
let mut source = std::fs::File::open(&ts.bin_path).unwrap();
56+
std::io::copy(&mut source, &mut file).unwrap();
57+
let raw_fd = file.as_raw_fd();
58+
59+
// memfd has AT_EXECFN as below. Fallback to argv[0]
60+
let res1 = Command::new(format!("/proc/self/fd/{raw_fd}"))
61+
.arg0("true")
62+
.output()
63+
.unwrap()
64+
.status
65+
.success();
66+
// fexecve uses /dev/fd which is symlink to /proc/self/fd/N
67+
let res2 = Command::new(format!("/dev/fd/{raw_fd}"))
68+
.arg0("true")
69+
.output()
70+
.unwrap()
71+
.status
72+
.success();
73+
assert!(res1 && res2);
74+
}
75+
4676
#[test]
4777
fn test_coreutils_help_ignore_args() {
4878
let scenario = TestScenario::new("help_ignoring_args");

0 commit comments

Comments
 (0)