@@ -32,6 +32,7 @@ pub fn run() -> io::Result<()> { |
| 32 | 32 | let _dbus_guard = acquire_dbus_name()?; |
| 33 | 33 | |
| 34 | 34 | let listener = UnixListener::bind(&paths.control_socket)?; |
| 35 | + set_control_socket_permissions(&paths.control_socket)?; |
| 35 | 36 | listener.set_nonblocking(true)?; |
| 36 | 37 | |
| 37 | 38 | logging::info("daemon_starting"); |
@@ -117,6 +118,37 @@ struct DaemonState { |
| 117 | 118 | fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<()> { |
| 118 | 119 | let mut reader = BufReader::new(stream); |
| 119 | 120 | let mut line = String::new(); |
| 121 | + |
| 122 | + let peer_uid = match peer_uid(reader.get_ref()) { |
| 123 | + Ok(uid) => uid, |
| 124 | + Err(error) => { |
| 125 | + let mapping = map_portal_error(&PortalError::InternalFailure); |
| 126 | + logging::warn(&format!("peer_identity_error={error}")); |
| 127 | + return write_response( |
| 128 | + reader.into_inner(), |
| 129 | + ControlResponse::Error { |
| 130 | + code: mapping.code as u32, |
| 131 | + reason: mapping.reason.to_string(), |
| 132 | + }, |
| 133 | + ); |
| 134 | + } |
| 135 | + }; |
| 136 | + if !is_trusted_control_peer(peer_uid) { |
| 137 | + let mapping = map_portal_error(&PortalError::UnauthorizedClient); |
| 138 | + logging::warn(&format!( |
| 139 | + "unauthorized_control_peer uid={} expected_uid={}", |
| 140 | + peer_uid, |
| 141 | + daemon_uid() |
| 142 | + )); |
| 143 | + return write_response( |
| 144 | + reader.into_inner(), |
| 145 | + ControlResponse::Error { |
| 146 | + code: mapping.code as u32, |
| 147 | + reason: mapping.reason.to_string(), |
| 148 | + }, |
| 149 | + ); |
| 150 | + } |
| 151 | + |
| 120 | 152 | let bytes_read = { |
| 121 | 153 | let mut limited = reader.by_ref().take((MAX_CONTROL_LINE_BYTES + 1) as u64); |
| 122 | 154 | limited.read_line(&mut line)? |
@@ -414,7 +446,32 @@ fn quarantine_request_store(path: &Path) -> io::Result<Option<std::path::PathBuf |
| 414 | 446 | Ok(Some(quarantined)) |
| 415 | 447 | } |
| 416 | 448 | |
| 449 | +fn set_control_socket_permissions(path: &Path) -> io::Result<()> { |
| 450 | + #[cfg(unix)] |
| 451 | + { |
| 452 | + use std::os::unix::fs::PermissionsExt; |
| 453 | + fs::set_permissions(path, fs::Permissions::from_mode(0o600))?; |
| 454 | + } |
| 455 | + Ok(()) |
| 456 | +} |
| 457 | + |
| 417 | 458 | fn trusted_sender(stream: &UnixStream) -> io::Result<String> { |
| 459 | + peer_uid(stream).map(canonical_sender_for_uid) |
| 460 | +} |
| 461 | + |
| 462 | +fn canonical_sender_for_uid(uid: u32) -> String { |
| 463 | + format!(":uid.{uid}") |
| 464 | +} |
| 465 | + |
| 466 | +fn is_trusted_control_peer(uid: u32) -> bool { |
| 467 | + uid == daemon_uid() |
| 468 | +} |
| 469 | + |
| 470 | +fn daemon_uid() -> u32 { |
| 471 | + unsafe { libc::geteuid() } |
| 472 | +} |
| 473 | + |
| 474 | +fn peer_uid(stream: &UnixStream) -> io::Result<u32> { |
| 418 | 475 | #[cfg(target_os = "linux")] |
| 419 | 476 | { |
| 420 | 477 | use std::mem; |
@@ -442,7 +499,7 @@ fn trusted_sender(stream: &UnixStream) -> io::Result<String> { |
| 442 | 499 | if len as usize != mem::size_of::<libc::ucred>() { |
| 443 | 500 | return Err(io::Error::other("invalid peer credential size")); |
| 444 | 501 | } |
| 445 | | - return Ok(canonical_sender_for_uid(cred.uid)); |
| 502 | + return Ok(cred.uid); |
| 446 | 503 | } |
| 447 | 504 | |
| 448 | 505 | #[cfg(not(target_os = "linux"))] |
@@ -455,10 +512,6 @@ fn trusted_sender(stream: &UnixStream) -> io::Result<String> { |
| 455 | 512 | } |
| 456 | 513 | } |
| 457 | 514 | |
| 458 | | -fn canonical_sender_for_uid(uid: u32) -> String { |
| 459 | | - format!(":uid.{uid}") |
| 460 | | -} |
| 461 | | - |
| 462 | 515 | fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) { |
| 463 | 516 | if let Err(error) = request_store::persist_registry(path, registry) { |
| 464 | 517 | logging::warn(&format!("request_store_write_failed error={error}")); |
@@ -469,7 +522,8 @@ fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) { |
| 469 | 522 | mod tests { |
| 470 | 523 | use super::{ |
| 471 | 524 | DaemonState, MAX_CONTROL_LINE_BYTES, canonical_sender_for_uid, handle_connection, |
| 472 | | - load_registry_with_fallback, load_registry_with_recovery, |
| 525 | + is_trusted_control_peer, load_registry_with_fallback, load_registry_with_recovery, |
| 526 | + peer_uid, set_control_socket_permissions, |
| 473 | 527 | }; |
| 474 | 528 | use garwarp_ipc::{ControlResponse, HealthStatus}; |
| 475 | 529 | use std::fs; |
@@ -493,6 +547,31 @@ mod tests { |
| 493 | 547 | canonical_sender_for_uid(unsafe { libc::geteuid() }) |
| 494 | 548 | } |
| 495 | 549 | |
| 550 | + #[cfg(target_os = "linux")] |
| 551 | + #[test] |
| 552 | + fn local_peer_uid_is_trusted() { |
| 553 | + let (_client, server) = UnixStream::pair().expect("pair should be created"); |
| 554 | + let uid = peer_uid(&server).expect("peer uid should be readable"); |
| 555 | + assert!(is_trusted_control_peer(uid)); |
| 556 | + } |
| 557 | + |
| 558 | + #[cfg(unix)] |
| 559 | + #[test] |
| 560 | + fn control_socket_permissions_are_private() { |
| 561 | + use std::os::unix::fs::PermissionsExt; |
| 562 | + |
| 563 | + let path = unique_temp_file(); |
| 564 | + fs::write(&path, "").expect("socket placeholder should be created"); |
| 565 | + set_control_socket_permissions(&path).expect("permissions should be set"); |
| 566 | + let mode = fs::metadata(&path) |
| 567 | + .expect("metadata should be readable") |
| 568 | + .permissions() |
| 569 | + .mode() |
| 570 | + & 0o777; |
| 571 | + assert_eq!(mode, 0o600); |
| 572 | + let _ = fs::remove_file(path); |
| 573 | + } |
| 574 | + |
| 496 | 575 | #[test] |
| 497 | 576 | fn status_request_returns_status_response() { |
| 498 | 577 | let (mut client, server) = UnixStream::pair().expect("pair should be created"); |