@@ -32,6 +32,7 @@ pub fn run() -> io::Result<()> { |
| 32 | let _dbus_guard = acquire_dbus_name()?; | 32 | let _dbus_guard = acquire_dbus_name()?; |
| 33 | | 33 | |
| 34 | let listener = UnixListener::bind(&paths.control_socket)?; | 34 | let listener = UnixListener::bind(&paths.control_socket)?; |
| | 35 | + set_control_socket_permissions(&paths.control_socket)?; |
| 35 | listener.set_nonblocking(true)?; | 36 | listener.set_nonblocking(true)?; |
| 36 | | 37 | |
| 37 | logging::info("daemon_starting"); | 38 | logging::info("daemon_starting"); |
@@ -117,6 +118,37 @@ struct DaemonState { |
| 117 | fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<()> { | 118 | fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<()> { |
| 118 | let mut reader = BufReader::new(stream); | 119 | let mut reader = BufReader::new(stream); |
| 119 | let mut line = String::new(); | 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 | let bytes_read = { | 152 | let bytes_read = { |
| 121 | let mut limited = reader.by_ref().take((MAX_CONTROL_LINE_BYTES + 1) as u64); | 153 | let mut limited = reader.by_ref().take((MAX_CONTROL_LINE_BYTES + 1) as u64); |
| 122 | limited.read_line(&mut line)? | 154 | limited.read_line(&mut line)? |
@@ -414,7 +446,32 @@ fn quarantine_request_store(path: &Path) -> io::Result<Option<std::path::PathBuf |
| 414 | Ok(Some(quarantined)) | 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 | fn trusted_sender(stream: &UnixStream) -> io::Result<String> { | 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 | #[cfg(target_os = "linux")] | 475 | #[cfg(target_os = "linux")] |
| 419 | { | 476 | { |
| 420 | use std::mem; | 477 | use std::mem; |
@@ -442,7 +499,7 @@ fn trusted_sender(stream: &UnixStream) -> io::Result<String> { |
| 442 | if len as usize != mem::size_of::<libc::ucred>() { | 499 | if len as usize != mem::size_of::<libc::ucred>() { |
| 443 | return Err(io::Error::other("invalid peer credential size")); | 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 | #[cfg(not(target_os = "linux"))] | 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 | fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) { | 515 | fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) { |
| 463 | if let Err(error) = request_store::persist_registry(path, registry) { | 516 | if let Err(error) = request_store::persist_registry(path, registry) { |
| 464 | logging::warn(&format!("request_store_write_failed error={error}")); | 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 | mod tests { | 522 | mod tests { |
| 470 | use super::{ | 523 | use super::{ |
| 471 | DaemonState, MAX_CONTROL_LINE_BYTES, canonical_sender_for_uid, handle_connection, | 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 | use garwarp_ipc::{ControlResponse, HealthStatus}; | 528 | use garwarp_ipc::{ControlResponse, HealthStatus}; |
| 475 | use std::fs; | 529 | use std::fs; |
@@ -493,6 +547,31 @@ mod tests { |
| 493 | canonical_sender_for_uid(unsafe { libc::geteuid() }) | 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 | #[test] | 575 | #[test] |
| 497 | fn status_request_returns_status_response() { | 576 | fn status_request_returns_status_response() { |
| 498 | let (mut client, server) = UnixStream::pair().expect("pair should be created"); | 577 | let (mut client, server) = UnixStream::pair().expect("pair should be created"); |