Skip to content

Commit 5e7d586

Browse files
jtraceysylvestre
authored andcommitted
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.
1 parent 45b29d2 commit 5e7d586

File tree

4 files changed

+53
-27
lines changed

4 files changed

+53
-27
lines changed

src/uu/id/src/id.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -508,15 +508,15 @@ fn pline(possible_uid: Option<uid_t>) {
508508
println!(
509509
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
510510
pw.name,
511-
pw.user_passwd,
511+
pw.user_passwd.unwrap_or_default(),
512512
pw.uid,
513513
pw.gid,
514-
pw.user_access_class,
514+
pw.user_access_class.unwrap_or_default(),
515515
pw.passwd_change_time,
516516
pw.expiration,
517-
pw.user_info,
518-
pw.user_dir,
519-
pw.user_shell
517+
pw.user_info.unwrap_or_default(),
518+
pw.user_dir.unwrap_or_default(),
519+
pw.user_shell.unwrap_or_default()
520520
);
521521
}
522522

@@ -527,7 +527,13 @@ fn pline(possible_uid: Option<uid_t>) {
527527

528528
println!(
529529
"{}:{}:{}:{}:{}:{}:{}",
530-
pw.name, pw.user_passwd, pw.uid, pw.gid, pw.user_info, pw.user_dir, pw.user_shell
530+
pw.name,
531+
pw.user_passwd.unwrap_or_default(),
532+
pw.uid,
533+
pw.gid,
534+
pw.user_info.unwrap_or_default(),
535+
pw.user_dir.unwrap_or_default(),
536+
pw.user_shell.unwrap_or_default()
531537
);
532538
}
533539

src/uu/pinky/src/pinky.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,16 @@ fn time_string(ut: &Utmpx) -> String {
245245
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C
246246
}
247247

248-
fn gecos_to_fullname(pw: &Passwd) -> String {
249-
let mut gecos = pw.user_info.clone();
248+
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
249+
let mut gecos = if let Some(gecos) = &pw.user_info {
250+
gecos.clone()
251+
} else {
252+
return None;
253+
};
250254
if let Some(n) = gecos.find(',') {
251255
gecos.truncate(n);
252256
}
253-
gecos.replace('&', &pw.name.capitalize())
257+
Some(gecos.replace('&', &pw.name.capitalize()))
254258
}
255259

256260
impl Pinky {
@@ -278,8 +282,13 @@ impl Pinky {
278282
print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user());
279283

280284
if self.include_fullname {
281-
if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
282-
print!(" {:<19.19}", gecos_to_fullname(&pw));
285+
let fullname = if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
286+
gecos_to_fullname(&pw)
287+
} else {
288+
None
289+
};
290+
if let Some(fullname) = fullname {
291+
print!(" {:<19.19}", fullname);
283292
} else {
284293
print!(" {:19}", " ???");
285294
}
@@ -341,21 +350,24 @@ impl Pinky {
341350
for u in &self.names {
342351
print!("Login name: {:<28}In real life: ", u);
343352
if let Ok(pw) = Passwd::locate(u.as_str()) {
344-
println!(" {}", gecos_to_fullname(&pw));
353+
let fullname = gecos_to_fullname(&pw).unwrap_or_default();
354+
let user_dir = pw.user_dir.unwrap_or_default();
355+
let user_shell = pw.user_shell.unwrap_or_default();
356+
println!(" {}", fullname);
345357
if self.include_home_and_shell {
346-
print!("Directory: {:<29}", pw.user_dir);
347-
println!("Shell: {}", pw.user_shell);
358+
print!("Directory: {:<29}", user_dir);
359+
println!("Shell: {}", user_shell);
348360
}
349361
if self.include_project {
350-
let mut p = PathBuf::from(&pw.user_dir);
362+
let mut p = PathBuf::from(&user_dir);
351363
p.push(".project");
352364
if let Ok(f) = File::open(p) {
353365
print!("Project: ");
354366
read_to_console(f);
355367
}
356368
}
357369
if self.include_plan {
358-
let mut p = PathBuf::from(&pw.user_dir);
370+
let mut p = PathBuf::from(&user_dir);
359371
p.push(".plan");
360372
if let Ok(f) = File::open(p) {
361373
println!("Plan:");

src/uucore/src/lib/features/entries.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,16 @@ pub struct Passwd {
147147
/// AKA passwd.pw_gid
148148
pub gid: gid_t,
149149
/// AKA passwd.pw_gecos
150-
pub user_info: String,
150+
pub user_info: Option<String>,
151151
/// AKA passwd.pw_shell
152-
pub user_shell: String,
152+
pub user_shell: Option<String>,
153153
/// AKA passwd.pw_dir
154-
pub user_dir: String,
154+
pub user_dir: Option<String>,
155155
/// AKA passwd.pw_passwd
156-
pub user_passwd: String,
156+
pub user_passwd: Option<String>,
157157
/// AKA passwd.pw_class
158158
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
159-
pub user_access_class: String,
159+
pub user_access_class: Option<String>,
160160
/// AKA passwd.pw_change
161161
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
162162
pub passwd_change_time: time_t,
@@ -166,16 +166,21 @@ pub struct Passwd {
166166
}
167167

168168
/// SAFETY: ptr must point to a valid C string.
169-
unsafe fn cstr2string(ptr: *const c_char) -> String {
170-
CStr::from_ptr(ptr).to_string_lossy().into_owned()
169+
/// Returns None if ptr is null.
170+
unsafe fn cstr2string(ptr: *const c_char) -> Option<String> {
171+
if !ptr.is_null() {
172+
Some(CStr::from_ptr(ptr).to_string_lossy().into_owned())
173+
} else {
174+
None
175+
}
171176
}
172177

173178
impl Passwd {
174179
/// SAFETY: All the pointed-to strings must be valid and not change while
175180
/// the function runs. That means PW_LOCK must be held.
176181
unsafe fn from_raw(raw: passwd) -> Self {
177182
Self {
178-
name: cstr2string(raw.pw_name),
183+
name: cstr2string(raw.pw_name).expect("passwd without name"),
179184
uid: raw.pw_uid,
180185
gid: raw.pw_gid,
181186
user_info: cstr2string(raw.pw_gecos),
@@ -243,7 +248,7 @@ impl Group {
243248
/// the function runs. That means PW_LOCK must be held.
244249
unsafe fn from_raw(raw: group) -> Self {
245250
Self {
246-
name: cstr2string(raw.gr_name),
251+
name: cstr2string(raw.gr_name).expect("group without name"),
247252
gid: raw.gr_gid,
248253
}
249254
}

tests/by-util/test_pinky.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ fn test_capitalize() {
2424
fn test_long_format() {
2525
let login = "root";
2626
let pw: Passwd = Passwd::locate(login).unwrap();
27-
let real_name = pw.user_info.replace('&', &pw.name.capitalize());
27+
let user_info = pw.user_info.unwrap_or_default();
28+
let user_dir = pw.user_dir.unwrap_or_default();
29+
let user_shell = pw.user_shell.unwrap_or_default();
30+
let real_name = user_info.replace('&', &pw.name.capitalize());
2831
let ts = TestScenario::new(util_name!());
2932
ts.ucmd().arg("-l").arg(login).succeeds().stdout_is(format!(
3033
"Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n",
31-
login, real_name, pw.user_dir, pw.user_shell
34+
login, real_name, user_dir, user_shell
3235
));
3336

3437
ts.ucmd()

0 commit comments

Comments
 (0)