walld: verify source_tty ownership
Build and test / check (push) Successful in 1m23s
Build and test / build (push) Successful in 2m28s
Build and test / test (push) Failing after 4m20s
Build and test / docs (push) Successful in 5m17s

This commit is contained in:
2026-07-30 20:29:10 +09:00
parent 6d382b0e8b
commit b23beecd21
2 changed files with 48 additions and 0 deletions
+20
View File
@@ -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())
})
+28
View File
@@ -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 {