fix install file with invalid uid (#7789)

* treat invalid user or group as uid or gid if it's an integer

* install: add test for install with invalid user or group
This commit is contained in:
Zhang Wen
2026-06-07 11:27:36 +02:00
committed by GitHub
parent e6a6cb6399
commit db86d3d668
2 changed files with 103 additions and 2 deletions
+16 -2
View File
@@ -405,7 +405,14 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
} else {
match usr2uid(&owner) {
Ok(u) => Some(u),
Err(_) => return Err(InstallError::InvalidUser(owner.clone()).into()),
// When using -o500 option and there's no user with uid 500 on the system
// usr2uid returns an Err value and the whole install operation fails.
// GNU coreutils installs a file with uid 500 in the same situation
// so just return the supplied owner as uid if it's an integer value
Err(_) => match owner.parse::<u32>() {
Ok(u) => Some(u),
Err(_) => return Err(InstallError::InvalidUser(owner.clone()).into()),
},
}
};
@@ -419,7 +426,14 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
} else {
match grp2gid(&group) {
Ok(g) => Some(g),
Err(_) => return Err(InstallError::InvalidGroup(group.clone()).into()),
// When using -g500 option and there's no group with gid 500 on the system
// grp2gid returns an Err value and the whole install operation fails.
// GNU coreutils installs a file with gid 500 in the same situation
// so just return the supplied group as gid if it's an integer value
Err(_) => match group.parse::<u32>() {
Ok(g) => Some(g),
Err(_) => return Err(InstallError::InvalidGroup(group.clone()).into()),
},
}
};
+87
View File
@@ -8,6 +8,10 @@
use filetime::FileTime;
use std::fs;
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
#[cfg(target_os = "linux")]
use std::os::unix::ffi::OsStringExt;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
#[cfg(not(windows))]
@@ -2695,3 +2699,86 @@ fn test_install_d_dangling_symlink_in_path_errors() {
"The symlink target must not have been created"
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_install_set_owner_nonexistent_uid_and_gid() {
let file = File::open("/etc/login.defs").unwrap();
let reader = BufReader::new(file);
let mut uid_min: u32 = 0;
let mut uid_max: u32 = 0;
let mut gid_min: u32 = 0;
let mut gid_max: u32 = 0;
for line in reader.lines() {
let line = line.unwrap();
if line.starts_with("UID_MIN") {
let tokens: Vec<&str> = line.split_whitespace().collect();
uid_min = tokens[1].parse().unwrap();
}
if line.starts_with("UID_MAX") {
let tokens: Vec<&str> = line.split_whitespace().collect();
uid_max = tokens[1].parse().unwrap();
}
if line.starts_with("GID_MIN") {
let tokens: Vec<&str> = line.split_whitespace().collect();
gid_min = tokens[1].parse().unwrap();
}
if line.starts_with("GID_MAX") {
let tokens: Vec<&str> = line.split_whitespace().collect();
gid_max = tokens[1].parse().unwrap();
}
}
let file = File::open("/etc/passwd").unwrap();
let reader = BufReader::new(file);
let mut uids: Vec<u32> = vec![];
let mut gids: Vec<u32> = vec![];
for line in reader.lines() {
let line = line.unwrap();
let tokens: Vec<&str> = line.split(':').collect();
let uid: u32 = tokens[2].parse().unwrap();
if (uid_min..=uid_max).contains(&uid) {
uids.push(uid);
}
let gid: u32 = tokens[3].parse().unwrap();
if (gid_min..=gid_max).contains(&gid) {
gids.push(gid);
}
}
uids.sort_unstable();
let next_uid = if let Some(uid) = uids.last() {
*uid + 1
} else {
uid_min
};
let next_gid = if let Some(gid) = gids.last() {
*gid + 1
} else {
gid_min
};
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
if let Ok(result) = run_ucmd_as_root(
&ts,
&[
format!("-o{next_uid}").as_str(),
format!("-g{next_gid}").as_str(),
"a",
"b",
],
) {
result.success();
assert!(at.file_exists("b"));
let metadata = fs::metadata(at.plus("b")).unwrap();
assert_eq!(metadata.uid(), next_uid);
assert_eq!(metadata.gid(), next_gid);
} else {
println!("Test skipped; requires root user");
}
}