mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
fix null pointer derefs
The code for creating a Passwd from the fields of the raw syscall result assumed that the syscall would return valid C strings in all non-error cases. This is not true, and at least one platform (Android) will populate the fields with null pointers where they are not supported. To fix this and prevent the error from happening again, this commit changes `cstr2string(ptr)` to check for a null pointer, and return an `Option<String>`, with `None` being the null pointer case. While arguably it should be the caller's job to check for a null pointer before calling (since the safety precondition is that the pointer is to a valid C string), relying on the type checker to force remembering this edge case is safer in the long run.
This commit is contained in:
parent
45b29d287f
commit
5e7d58650d
4 changed files with 53 additions and 27 deletions
|
@ -508,15 +508,15 @@ fn pline(possible_uid: Option<uid_t>) {
|
||||||
println!(
|
println!(
|
||||||
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
|
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
|
||||||
pw.name,
|
pw.name,
|
||||||
pw.user_passwd,
|
pw.user_passwd.unwrap_or_default(),
|
||||||
pw.uid,
|
pw.uid,
|
||||||
pw.gid,
|
pw.gid,
|
||||||
pw.user_access_class,
|
pw.user_access_class.unwrap_or_default(),
|
||||||
pw.passwd_change_time,
|
pw.passwd_change_time,
|
||||||
pw.expiration,
|
pw.expiration,
|
||||||
pw.user_info,
|
pw.user_info.unwrap_or_default(),
|
||||||
pw.user_dir,
|
pw.user_dir.unwrap_or_default(),
|
||||||
pw.user_shell
|
pw.user_shell.unwrap_or_default()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,7 +527,13 @@ fn pline(possible_uid: Option<uid_t>) {
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"{}:{}:{}:{}:{}:{}:{}",
|
"{}:{}:{}:{}:{}:{}:{}",
|
||||||
pw.name, pw.user_passwd, pw.uid, pw.gid, pw.user_info, pw.user_dir, pw.user_shell
|
pw.name,
|
||||||
|
pw.user_passwd.unwrap_or_default(),
|
||||||
|
pw.uid,
|
||||||
|
pw.gid,
|
||||||
|
pw.user_info.unwrap_or_default(),
|
||||||
|
pw.user_dir.unwrap_or_default(),
|
||||||
|
pw.user_shell.unwrap_or_default()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,12 +245,16 @@ fn time_string(ut: &Utmpx) -> String {
|
||||||
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C
|
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gecos_to_fullname(pw: &Passwd) -> String {
|
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
|
||||||
let mut gecos = pw.user_info.clone();
|
let mut gecos = if let Some(gecos) = &pw.user_info {
|
||||||
|
gecos.clone()
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(n) = gecos.find(',') {
|
if let Some(n) = gecos.find(',') {
|
||||||
gecos.truncate(n);
|
gecos.truncate(n);
|
||||||
}
|
}
|
||||||
gecos.replace('&', &pw.name.capitalize())
|
Some(gecos.replace('&', &pw.name.capitalize()))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pinky {
|
impl Pinky {
|
||||||
|
@ -278,8 +282,13 @@ impl Pinky {
|
||||||
print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user());
|
print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user());
|
||||||
|
|
||||||
if self.include_fullname {
|
if self.include_fullname {
|
||||||
if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
|
let fullname = if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
|
||||||
print!(" {:<19.19}", gecos_to_fullname(&pw));
|
gecos_to_fullname(&pw)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(fullname) = fullname {
|
||||||
|
print!(" {:<19.19}", fullname);
|
||||||
} else {
|
} else {
|
||||||
print!(" {:19}", " ???");
|
print!(" {:19}", " ???");
|
||||||
}
|
}
|
||||||
|
@ -341,13 +350,16 @@ impl Pinky {
|
||||||
for u in &self.names {
|
for u in &self.names {
|
||||||
print!("Login name: {:<28}In real life: ", u);
|
print!("Login name: {:<28}In real life: ", u);
|
||||||
if let Ok(pw) = Passwd::locate(u.as_str()) {
|
if let Ok(pw) = Passwd::locate(u.as_str()) {
|
||||||
println!(" {}", gecos_to_fullname(&pw));
|
let fullname = gecos_to_fullname(&pw).unwrap_or_default();
|
||||||
|
let user_dir = pw.user_dir.unwrap_or_default();
|
||||||
|
let user_shell = pw.user_shell.unwrap_or_default();
|
||||||
|
println!(" {}", fullname);
|
||||||
if self.include_home_and_shell {
|
if self.include_home_and_shell {
|
||||||
print!("Directory: {:<29}", pw.user_dir);
|
print!("Directory: {:<29}", user_dir);
|
||||||
println!("Shell: {}", pw.user_shell);
|
println!("Shell: {}", user_shell);
|
||||||
}
|
}
|
||||||
if self.include_project {
|
if self.include_project {
|
||||||
let mut p = PathBuf::from(&pw.user_dir);
|
let mut p = PathBuf::from(&user_dir);
|
||||||
p.push(".project");
|
p.push(".project");
|
||||||
if let Ok(f) = File::open(p) {
|
if let Ok(f) = File::open(p) {
|
||||||
print!("Project: ");
|
print!("Project: ");
|
||||||
|
@ -355,7 +367,7 @@ impl Pinky {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.include_plan {
|
if self.include_plan {
|
||||||
let mut p = PathBuf::from(&pw.user_dir);
|
let mut p = PathBuf::from(&user_dir);
|
||||||
p.push(".plan");
|
p.push(".plan");
|
||||||
if let Ok(f) = File::open(p) {
|
if let Ok(f) = File::open(p) {
|
||||||
println!("Plan:");
|
println!("Plan:");
|
||||||
|
|
|
@ -147,16 +147,16 @@ pub struct Passwd {
|
||||||
/// AKA passwd.pw_gid
|
/// AKA passwd.pw_gid
|
||||||
pub gid: gid_t,
|
pub gid: gid_t,
|
||||||
/// AKA passwd.pw_gecos
|
/// AKA passwd.pw_gecos
|
||||||
pub user_info: String,
|
pub user_info: Option<String>,
|
||||||
/// AKA passwd.pw_shell
|
/// AKA passwd.pw_shell
|
||||||
pub user_shell: String,
|
pub user_shell: Option<String>,
|
||||||
/// AKA passwd.pw_dir
|
/// AKA passwd.pw_dir
|
||||||
pub user_dir: String,
|
pub user_dir: Option<String>,
|
||||||
/// AKA passwd.pw_passwd
|
/// AKA passwd.pw_passwd
|
||||||
pub user_passwd: String,
|
pub user_passwd: Option<String>,
|
||||||
/// AKA passwd.pw_class
|
/// AKA passwd.pw_class
|
||||||
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
||||||
pub user_access_class: String,
|
pub user_access_class: Option<String>,
|
||||||
/// AKA passwd.pw_change
|
/// AKA passwd.pw_change
|
||||||
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
||||||
pub passwd_change_time: time_t,
|
pub passwd_change_time: time_t,
|
||||||
|
@ -166,8 +166,13 @@ pub struct Passwd {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SAFETY: ptr must point to a valid C string.
|
/// SAFETY: ptr must point to a valid C string.
|
||||||
unsafe fn cstr2string(ptr: *const c_char) -> String {
|
/// Returns None if ptr is null.
|
||||||
CStr::from_ptr(ptr).to_string_lossy().into_owned()
|
unsafe fn cstr2string(ptr: *const c_char) -> Option<String> {
|
||||||
|
if !ptr.is_null() {
|
||||||
|
Some(CStr::from_ptr(ptr).to_string_lossy().into_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Passwd {
|
impl Passwd {
|
||||||
|
@ -175,7 +180,7 @@ impl Passwd {
|
||||||
/// the function runs. That means PW_LOCK must be held.
|
/// the function runs. That means PW_LOCK must be held.
|
||||||
unsafe fn from_raw(raw: passwd) -> Self {
|
unsafe fn from_raw(raw: passwd) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: cstr2string(raw.pw_name),
|
name: cstr2string(raw.pw_name).expect("passwd without name"),
|
||||||
uid: raw.pw_uid,
|
uid: raw.pw_uid,
|
||||||
gid: raw.pw_gid,
|
gid: raw.pw_gid,
|
||||||
user_info: cstr2string(raw.pw_gecos),
|
user_info: cstr2string(raw.pw_gecos),
|
||||||
|
@ -243,7 +248,7 @@ impl Group {
|
||||||
/// the function runs. That means PW_LOCK must be held.
|
/// the function runs. That means PW_LOCK must be held.
|
||||||
unsafe fn from_raw(raw: group) -> Self {
|
unsafe fn from_raw(raw: group) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: cstr2string(raw.gr_name),
|
name: cstr2string(raw.gr_name).expect("group without name"),
|
||||||
gid: raw.gr_gid,
|
gid: raw.gr_gid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,14 @@ fn test_capitalize() {
|
||||||
fn test_long_format() {
|
fn test_long_format() {
|
||||||
let login = "root";
|
let login = "root";
|
||||||
let pw: Passwd = Passwd::locate(login).unwrap();
|
let pw: Passwd = Passwd::locate(login).unwrap();
|
||||||
let real_name = pw.user_info.replace('&', &pw.name.capitalize());
|
let user_info = pw.user_info.unwrap_or_default();
|
||||||
|
let user_dir = pw.user_dir.unwrap_or_default();
|
||||||
|
let user_shell = pw.user_shell.unwrap_or_default();
|
||||||
|
let real_name = user_info.replace('&', &pw.name.capitalize());
|
||||||
let ts = TestScenario::new(util_name!());
|
let ts = TestScenario::new(util_name!());
|
||||||
ts.ucmd().arg("-l").arg(login).succeeds().stdout_is(format!(
|
ts.ucmd().arg("-l").arg(login).succeeds().stdout_is(format!(
|
||||||
"Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n",
|
"Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n",
|
||||||
login, real_name, pw.user_dir, pw.user_shell
|
login, real_name, user_dir, user_shell
|
||||||
));
|
));
|
||||||
|
|
||||||
ts.ucmd()
|
ts.ucmd()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue