walld: verify source_tty ownership
This commit is contained in:
@@ -148,6 +148,22 @@ fn hostname() -> String {
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
}
|
||||
|
||||
/// Verifies that `source_tty` (if given) belongs to `source_uid`.
|
||||
fn verify_source_tty(
|
||||
source_tty: Option<&str>,
|
||||
source_uid: u32,
|
||||
) -> Result<(), VarlinkWalldClientError> {
|
||||
let Some(tty) = source_tty else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
tty_utils::verify_tty_owner(tty, source_uid).map_err(|err| {
|
||||
VarlinkWalldClientError::InvalidRequest {
|
||||
message: format!("source_tty {tty:?}: {err}"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn tty_error_to_walld_error(user: &str, tty: &str, err: TtyError) -> VarlinkWalldClientError {
|
||||
match err {
|
||||
TtyError::NotFound(_) | TtyError::InvalidName(_) | TtyError::NotCharacterDevice(_) => {
|
||||
@@ -239,6 +255,8 @@ pub async fn handle_wall_request(
|
||||
});
|
||||
}
|
||||
|
||||
verify_source_tty(source_tty.as_deref(), source_uid)?;
|
||||
|
||||
authorize(polkit, |authority| {
|
||||
check_wall(authority, source_pid, group.as_deref())
|
||||
})
|
||||
@@ -284,6 +302,8 @@ pub async fn handle_write_request(
|
||||
tty: Option<String>,
|
||||
message: String,
|
||||
) -> Result<VarlinkWriteResponse, VarlinkWalldClientError> {
|
||||
verify_source_tty(source_tty.as_deref(), source_uid)?;
|
||||
|
||||
authorize(polkit, |authority| {
|
||||
check_write(authority, source_pid, &user, tty.as_deref())
|
||||
})
|
||||
|
||||
@@ -33,6 +33,8 @@ pub enum TtyError {
|
||||
Unavailable(String),
|
||||
#[error("timed out writing to {0}")]
|
||||
Timeout(String),
|
||||
#[error("{0}: not owned by the caller")]
|
||||
NotOwnedByCaller(String),
|
||||
#[error("{path}: {source}")]
|
||||
Io {
|
||||
path: String,
|
||||
@@ -89,6 +91,32 @@ pub fn stat_tty(name: &str) -> Result<TtyInfo, TtyError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Verifies that `path` is a character device in `/dev/` owned by `uid`.
|
||||
pub fn verify_tty_owner(path: &str, uid: u32) -> Result<(), TtyError> {
|
||||
if !path.starts_with("/dev/") || path.split('/').any(|part| part == "..") {
|
||||
return Err(TtyError::InvalidName(path.to_string()));
|
||||
}
|
||||
|
||||
let st = stat(Path::new(path)).map_err(|e| match e {
|
||||
Errno::ENOENT => TtyError::NotFound(path.to_string()),
|
||||
other => TtyError::Io {
|
||||
path: path.to_string(),
|
||||
source: other.into(),
|
||||
},
|
||||
})?;
|
||||
|
||||
let file_type = SFlag::from_bits_truncate(st.st_mode) & SFlag::S_IFMT;
|
||||
if file_type != SFlag::S_IFCHR {
|
||||
return Err(TtyError::NotCharacterDevice(path.to_string()));
|
||||
}
|
||||
|
||||
if st.st_uid != uid {
|
||||
return Err(TtyError::NotOwnedByCaller(path.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A single logged-in session, as reported by utmpx (systemd-logind)
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Session {
|
||||
|
||||
Reference in New Issue
Block a user