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<()> {
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");
garwarp/src/error.rsmodified
@@ -14,6 +14,7 @@ pub enum PortalResponseCode {
14
 pub enum PortalError {
14
 pub enum PortalError {
15
     CancelledByUser,
15
     CancelledByUser,
16
     InvalidRequestPayload,
16
     InvalidRequestPayload,
17
+    UnauthorizedClient,
17
     InvalidParentWindow,
18
     InvalidParentWindow,
18
     OwnershipMismatch,
19
     OwnershipMismatch,
19
     RequestNotFound,
20
     RequestNotFound,
@@ -40,6 +41,10 @@ pub fn map_portal_error(error: &PortalError) -> ErrorMapping {
40
             code: PortalResponseCode::Failed,
41
             code: PortalResponseCode::Failed,
41
             reason: "invalid_request",
42
             reason: "invalid_request",
42
         },
43
         },
44
+        PortalError::UnauthorizedClient => ErrorMapping {
45
+            code: PortalResponseCode::Failed,
46
+            reason: "unauthorized_client",
47
+        },
43
         PortalError::InvalidParentWindow => ErrorMapping {
48
         PortalError::InvalidParentWindow => ErrorMapping {
44
             code: PortalResponseCode::Failed,
49
             code: PortalResponseCode::Failed,
45
             reason: "invalid_parent_window",
50
             reason: "invalid_parent_window",
@@ -118,6 +123,7 @@ mod tests {
118
         let errors = [
123
         let errors = [
119
             PortalError::CancelledByUser,
124
             PortalError::CancelledByUser,
120
             PortalError::InvalidRequestPayload,
125
             PortalError::InvalidRequestPayload,
126
+            PortalError::UnauthorizedClient,
121
             PortalError::InvalidParentWindow,
127
             PortalError::InvalidParentWindow,
122
             PortalError::OwnershipMismatch,
128
             PortalError::OwnershipMismatch,
123
             PortalError::RequestNotFound,
129
             PortalError::RequestNotFound,