Merge branch 'passwd_no_setuid' into 'master'

passwd: Remove suid usage though the sudo daemon

See merge request redox-os/userutils!55
This commit is contained in:
Jeremy Soller
2025-04-20 19:58:06 +00:00
2 changed files with 109 additions and 31 deletions
+74 -31
View File
@@ -6,6 +6,7 @@ use std::io::Write;
use std::process::exit;
use extra::option::OptionalExt;
use libc::{EPERM, O_CLOEXEC};
use redox_users::{All, AllUsers, Config, get_uid};
use termion::input::TermRead;
@@ -77,20 +78,30 @@ fn main() {
}
let uid = get_uid().unwrap_or_exit(1);
let mut users = AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let user = match args.value_of("LOGIN") {
Some(login) => users.get_mut_by_name(login).unwrap_or_else(|| {
eprintln!("passwd: user does not exist: {}", login);
exit(1);
}),
None => users.get_mut_by_id(uid).unwrap_or_else(|| {
eprintln!("passwd: you do not exist");
exit(1);
}),
};
if uid == 0 {
let mut users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
if user.uid != uid && uid != 0 {
let user = find_user(&args, &mut users);
let msg = format!("changing password for '{}' \n", user.user);
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let new_password = ask_new_password(stdin, stdout, stderr);
user.set_passwd(&new_password).unwrap_or_exit(1);
users.save().unwrap_or_exit(1);
return;
}
let mut users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let user = find_user(&args, &mut users);
if user.uid != uid {
eprintln!(
"passwd: you do not have permission to set the password of '{}'",
user.user
@@ -102,30 +113,64 @@ fn main() {
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let mut verified = false;
if user.is_passwd_blank() {
verified = true;
} else if user.is_passwd_unset() && uid != 0 {
verified = false;
} else if user.uid == uid || uid != 0 {
stdout.write_all(b"current password: ").r#try(&mut stderr);
drop(users); // Unlock /etc/passwd
stdout.write_all(b"current password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let file = libredox::call::open("/scheme/sudo/passwd", O_CLOEXEC, 0).unwrap();
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
verified = user.verify_passwd(&password)
match libredox::call::write(file, password.as_bytes()) {
Ok(_) => {}
Err(err) if err.errno() == EPERM => {
eprintln!("passwd: incorrect current password");
exit(1);
}
Err(err) => panic!("{err}"),
}
} else {
verified = true;
}
if !verified {
eprintln!("passwd: incorrect current password");
exit(1);
}
let new_password = ask_new_password(stdin, stdout, stderr);
match libredox::call::write(file, new_password.as_bytes()) {
Ok(_) => {}
Err(err) if err.errno() == EPERM => {
eprintln!("passwd: invalid new password");
exit(1);
}
Err(err) => panic!("{err}"),
}
}
fn find_user<'a, T: Default>(
args: &clap::ArgMatches<'_>,
users: &'a mut AllUsers<T>,
) -> &'a mut redox_users::User<T> {
let uid = get_uid().unwrap_or_exit(1);
match args.value_of("LOGIN") {
Some(login) => users.get_mut_by_name(login).unwrap_or_else(|| {
eprintln!("passwd: user does not exist: {}", login);
exit(1);
}),
None => users.get_mut_by_id(uid).unwrap_or_else(|| {
eprintln!("passwd: you do not exist");
exit(1);
}),
}
}
fn ask_new_password(
mut stdin: io::StdinLock<'_>,
mut stdout: io::StdoutLock<'_>,
mut stderr: io::Stderr,
) -> String {
stdout.write_all(b"new password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let Some(new_password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) else {
@@ -147,7 +192,5 @@ fn main() {
eprintln!("passwd: new password does not match confirm password");
exit(1);
}
user.set_passwd(&new_password).unwrap_or_exit(1);
users.save().unwrap_or_exit(1);
new_password
}
+35
View File
@@ -152,6 +152,10 @@ enum Handle {
AwaitingPassword { uid: u32 },
AwaitingRootPassword,
AwaitingContextFd,
AwaitingPasswordForPasswd { uid: u32 },
AwaitingNewPassword { uid: u32 },
Placeholder,
}
@@ -162,6 +166,7 @@ impl SchemeSync for Scheme {
let handle = match path {
"" => Handle::AwaitingPassword { uid: ctx.uid },
"su" => Handle::AwaitingRootPassword,
"passwd" => Handle::AwaitingPasswordForPasswd { uid: ctx.uid },
_ => return Err(Error::new(ENOENT)),
};
self.handles.insert(fd, handle);
@@ -222,6 +227,36 @@ impl SchemeSync for Scheme {
return Err(Error::new(EINVAL));
}
Handle::AwaitingPasswordForPasswd { uid } => {
let users =
AllUsers::authenticator(Config::default()).map_err(|_| Error::new(ENOLCK))?;
let user = users.get_by_id(uid as usize).ok_or(Error::new(EEXIST))?;
let password = validate_utf8(buf)?;
if user.verify_passwd(&password) {
*handle = Handle::AwaitingNewPassword { uid }
} else {
*handle = Handle::AwaitingPasswordForPasswd { uid };
return Err(Error::new(EPERM));
}
}
Handle::AwaitingNewPassword { uid } => {
let mut users = AllUsers::authenticator(Config::default().writeable(true))
.map_err(|_| Error::new(ENOLCK))?;
let user = users
.get_mut_by_id(uid as usize)
.ok_or(Error::new(EEXIST))?;
let new_password = validate_utf8(buf)?;
if user.set_passwd(&new_password).is_ok() {
users.save().map_err(|_| Error::new(ENOLCK))?;
*handle = Handle::Placeholder
} else {
*handle = Handle::AwaitingNewPassword { uid };
return Err(Error::new(EPERM));
}
}
Handle::Placeholder => {
eprintln!("sudo: found placeholder handle with ID {id}");
return Err(Error::new(EBADFD));