gardesk/garwarp / 921cd0e

Browse files

enforce trusted control peers

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
921cd0e939476af41f09b9e5aa3bea0a16b51a1b
Parents
47f3231
Tree
8570783

2 changed files

StatusFile+-
M garwarp/src/daemon.rs 85 6
M garwarp/src/error.rs 6 0
garwarp/src/daemon.rsmodified
@@ -32,6 +32,7 @@ pub fn run() -> io::Result<()> {
3232
     let _dbus_guard = acquire_dbus_name()?;
3333
 
3434
     let listener = UnixListener::bind(&paths.control_socket)?;
35
+    set_control_socket_permissions(&paths.control_socket)?;
3536
     listener.set_nonblocking(true)?;
3637
 
3738
     logging::info("daemon_starting");
@@ -117,6 +118,37 @@ struct DaemonState {
117118
 fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<()> {
118119
     let mut reader = BufReader::new(stream);
119120
     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
+
120152
     let bytes_read = {
121153
         let mut limited = reader.by_ref().take((MAX_CONTROL_LINE_BYTES + 1) as u64);
122154
         limited.read_line(&mut line)?
@@ -414,7 +446,32 @@ fn quarantine_request_store(path: &Path) -> io::Result<Option<std::path::PathBuf
414446
     Ok(Some(quarantined))
415447
 }
416448
 
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
+
417458
 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> {
418475
     #[cfg(target_os = "linux")]
419476
     {
420477
         use std::mem;
@@ -442,7 +499,7 @@ fn trusted_sender(stream: &UnixStream) -> io::Result<String> {
442499
         if len as usize != mem::size_of::<libc::ucred>() {
443500
             return Err(io::Error::other("invalid peer credential size"));
444501
         }
445
-        return Ok(canonical_sender_for_uid(cred.uid));
502
+        return Ok(cred.uid);
446503
     }
447504
 
448505
     #[cfg(not(target_os = "linux"))]
@@ -455,10 +512,6 @@ fn trusted_sender(stream: &UnixStream) -> io::Result<String> {
455512
     }
456513
 }
457514
 
458
-fn canonical_sender_for_uid(uid: u32) -> String {
459
-    format!(":uid.{uid}")
460
-}
461
-
462515
 fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) {
463516
     if let Err(error) = request_store::persist_registry(path, registry) {
464517
         logging::warn(&format!("request_store_write_failed error={error}"));
@@ -469,7 +522,8 @@ fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) {
469522
 mod tests {
470523
     use super::{
471524
         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,
473527
     };
474528
     use garwarp_ipc::{ControlResponse, HealthStatus};
475529
     use std::fs;
@@ -493,6 +547,31 @@ mod tests {
493547
         canonical_sender_for_uid(unsafe { libc::geteuid() })
494548
     }
495549
 
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
+
496575
     #[test]
497576
     fn status_request_returns_status_response() {
498577
         let (mut client, server) = UnixStream::pair().expect("pair should be created");
garwarp/src/error.rsmodified
@@ -14,6 +14,7 @@ pub enum PortalResponseCode {
1414
 pub enum PortalError {
1515
     CancelledByUser,
1616
     InvalidRequestPayload,
17
+    UnauthorizedClient,
1718
     InvalidParentWindow,
1819
     OwnershipMismatch,
1920
     RequestNotFound,
@@ -40,6 +41,10 @@ pub fn map_portal_error(error: &PortalError) -> ErrorMapping {
4041
             code: PortalResponseCode::Failed,
4142
             reason: "invalid_request",
4243
         },
44
+        PortalError::UnauthorizedClient => ErrorMapping {
45
+            code: PortalResponseCode::Failed,
46
+            reason: "unauthorized_client",
47
+        },
4348
         PortalError::InvalidParentWindow => ErrorMapping {
4449
             code: PortalResponseCode::Failed,
4550
             reason: "invalid_parent_window",
@@ -118,6 +123,7 @@ mod tests {
118123
         let errors = [
119124
             PortalError::CancelledByUser,
120125
             PortalError::InvalidRequestPayload,
126
+            PortalError::UnauthorizedClient,
121127
             PortalError::InvalidParentWindow,
122128
             PortalError::OwnershipMismatch,
123129
             PortalError::RequestNotFound,