gardesk/garwarp / 47f3231

Browse files

derive sender from peer credentials

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
47f323179285e041c2c9de441e7b257f37796037
Parents
9e7714d
Tree
f23282f

3 changed files

StatusFile+-
M Cargo.lock 1 0
M garwarp/Cargo.toml 1 0
M garwarp/src/daemon.rs 107 24
Cargo.lockmodified
@@ -303,6 +303,7 @@ name = "garwarp"
303303
 version = "0.1.0"
304304
 dependencies = [
305305
  "garwarp-ipc",
306
+ "libc",
306307
  "zbus",
307308
 ]
308309
 
garwarp/Cargo.tomlmodified
@@ -6,3 +6,4 @@ edition = "2024"
66
 [dependencies]
77
 garwarp-ipc = { path = "../garwarp-ipc" }
88
 zbus = "5.14.0"
9
+libc = "0.2"
garwarp/src/daemon.rsmodified
@@ -187,10 +187,25 @@ fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<
187187
         }
188188
         Some(ControlRequest::BeginRequest {
189189
             id,
190
-            sender,
190
+            sender: _sender,
191191
             app_id,
192192
             parent_window,
193193
         }) => {
194
+            let sender = match trusted_sender(reader.get_ref()) {
195
+                Ok(sender) => sender,
196
+                Err(error) => {
197
+                    let mapping = map_portal_error(&PortalError::InternalFailure);
198
+                    logging::warn(&format!("peer_identity_error={error}"));
199
+                    return write_response(
200
+                        reader.into_inner(),
201
+                        ControlResponse::Error {
202
+                            code: mapping.code as u32,
203
+                            reason: mapping.reason.to_string(),
204
+                        },
205
+                    );
206
+                }
207
+            };
208
+
194209
             let validation = validate_request_identity(&id, &sender, app_id.as_deref());
195210
             if let Err(error) = validation {
196211
                 let mapping = map_portal_error(&error);
@@ -266,10 +281,25 @@ fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<
266281
         }
267282
         Some(ControlRequest::TransitionRequest {
268283
             id,
269
-            sender,
284
+            sender: _sender,
270285
             app_id,
271286
             target,
272287
         }) => {
288
+            let sender = match trusted_sender(reader.get_ref()) {
289
+                Ok(sender) => sender,
290
+                Err(error) => {
291
+                    let mapping = map_portal_error(&PortalError::InternalFailure);
292
+                    logging::warn(&format!("peer_identity_error={error}"));
293
+                    return write_response(
294
+                        reader.into_inner(),
295
+                        ControlResponse::Error {
296
+                            code: mapping.code as u32,
297
+                            reason: mapping.reason.to_string(),
298
+                        },
299
+                    );
300
+                }
301
+            };
302
+
273303
             let validation = validate_request_identity(&id, &sender, app_id.as_deref());
274304
             if let Err(error) = validation {
275305
                 let mapping = map_portal_error(&error);
@@ -384,6 +414,51 @@ fn quarantine_request_store(path: &Path) -> io::Result<Option<std::path::PathBuf
384414
     Ok(Some(quarantined))
385415
 }
386416
 
417
+fn trusted_sender(stream: &UnixStream) -> io::Result<String> {
418
+    #[cfg(target_os = "linux")]
419
+    {
420
+        use std::mem;
421
+        use std::os::fd::AsRawFd;
422
+
423
+        let fd = stream.as_raw_fd();
424
+        let mut cred = libc::ucred {
425
+            pid: 0,
426
+            uid: 0,
427
+            gid: 0,
428
+        };
429
+        let mut len = mem::size_of::<libc::ucred>() as libc::socklen_t;
430
+        let rc = unsafe {
431
+            libc::getsockopt(
432
+                fd,
433
+                libc::SOL_SOCKET,
434
+                libc::SO_PEERCRED,
435
+                (&mut cred as *mut libc::ucred).cast::<libc::c_void>(),
436
+                &mut len,
437
+            )
438
+        };
439
+        if rc == -1 {
440
+            return Err(io::Error::last_os_error());
441
+        }
442
+        if len as usize != mem::size_of::<libc::ucred>() {
443
+            return Err(io::Error::other("invalid peer credential size"));
444
+        }
445
+        return Ok(canonical_sender_for_uid(cred.uid));
446
+    }
447
+
448
+    #[cfg(not(target_os = "linux"))]
449
+    {
450
+        let _ = stream;
451
+        Err(io::Error::new(
452
+            io::ErrorKind::Unsupported,
453
+            "peer credentials are unsupported on this platform",
454
+        ))
455
+    }
456
+}
457
+
458
+fn canonical_sender_for_uid(uid: u32) -> String {
459
+    format!(":uid.{uid}")
460
+}
461
+
387462
 fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) {
388463
     if let Err(error) = request_store::persist_registry(path, registry) {
389464
         logging::warn(&format!("request_store_write_failed error={error}"));
@@ -393,8 +468,8 @@ fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) {
393468
 #[cfg(test)]
394469
 mod tests {
395470
     use super::{
396
-        DaemonState, MAX_CONTROL_LINE_BYTES, handle_connection, load_registry_with_fallback,
397
-        load_registry_with_recovery,
471
+        DaemonState, MAX_CONTROL_LINE_BYTES, canonical_sender_for_uid, handle_connection,
472
+        load_registry_with_fallback, load_registry_with_recovery,
398473
     };
399474
     use garwarp_ipc::{ControlResponse, HealthStatus};
400475
     use std::fs;
@@ -414,6 +489,10 @@ mod tests {
414489
         std::env::temp_dir().join(format!("garwarp-daemon-recovery-{nanos}.state"))
415490
     }
416491
 
492
+    fn local_sender() -> String {
493
+        canonical_sender_for_uid(unsafe { libc::geteuid() })
494
+    }
495
+
417496
     #[test]
418497
     fn status_request_returns_status_response() {
419498
         let (mut client, server) = UnixStream::pair().expect("pair should be created");
@@ -430,7 +509,7 @@ mod tests {
430509
             .requests
431510
             .begin_at(
432511
                 "req-1",
433
-                RequestOwner::new(":1.2", None),
512
+                RequestOwner::new(local_sender(), None),
434513
                 None,
435514
                 Instant::now(),
436515
             )
@@ -439,7 +518,7 @@ mod tests {
439518
             .requests
440519
             .transition(
441520
                 "req-1",
442
-                &RequestOwner::new(":1.2", None),
521
+                &RequestOwner::new(local_sender(), None),
443522
                 RequestState::AwaitingUser,
444523
             )
445524
             .expect("request should transition");
@@ -704,7 +783,7 @@ mod tests {
704783
             .requests
705784
             .begin_at(
706785
                 "req-1",
707
-                RequestOwner::new(":1.2", None),
786
+                RequestOwner::new(local_sender(), None),
708787
                 Some(ParentWindowContext::X11 { window_id: 42 }),
709788
                 Instant::now(),
710789
             )
@@ -713,7 +792,7 @@ mod tests {
713792
             .requests
714793
             .transition(
715794
                 "req-1",
716
-                &RequestOwner::new(":1.2", None),
795
+                &RequestOwner::new(local_sender(), None),
717796
                 RequestState::AwaitingUser,
718797
             )
719798
             .expect("request should transition");
@@ -751,7 +830,7 @@ mod tests {
751830
             .requests
752831
             .begin_at(
753832
                 "req-1",
754
-                RequestOwner::new(":1.2", None),
833
+                RequestOwner::new(":uid.4242", None),
755834
                 Some(ParentWindowContext::X11 { window_id: 42 }),
756835
                 Instant::now(),
757836
             )
@@ -832,18 +911,18 @@ mod tests {
832911
     }
833912
 
834913
     #[test]
835
-    fn invalid_sender_maps_to_invalid_request() {
914
+    fn payload_sender_is_ignored_for_begin() {
836915
         let (mut client, server) = UnixStream::pair().expect("pair should be created");
837916
         client
838
-            .write_all(b"transition id=req-1 sender=org.test.App state=cancelled\n")
839
-            .expect("transition request should be written");
917
+            .write_all(b"begin id=req-1 sender=org.test.App parent=x11:0x2a\n")
918
+            .expect("begin request should be written");
840919
 
841920
         let mut state = DaemonState {
842921
             health: HealthStatus::Healthy,
843922
             requests: RequestRegistry::new(Duration::from_secs(5)),
844923
             running: true,
845924
         };
846
-        handle_connection(server, &mut state).expect("transition should be handled");
925
+        handle_connection(server, &mut state).expect("begin should be handled");
847926
 
848927
         let mut response_line = String::new();
849928
         let mut reader = BufReader::new(client);
@@ -853,11 +932,15 @@ mod tests {
853932
         let response = ControlResponse::parse_line(&response_line).expect("response should parse");
854933
         assert_eq!(
855934
             response,
856
-            ControlResponse::Error {
857
-                code: 2,
858
-                reason: "invalid_request".to_string(),
935
+            ControlResponse::AckRequest {
936
+                id: "req-1".to_string(),
937
+                state: "pending".to_string(),
859938
             }
860939
         );
940
+        assert_eq!(
941
+            state.requests.owner("req-1").map(|owner| owner.sender),
942
+            Some(local_sender())
943
+        );
861944
     }
862945
 
863946
     #[test]
@@ -876,7 +959,7 @@ mod tests {
876959
             .requests
877960
             .begin_at(
878961
                 "req-1",
879
-                RequestOwner::new(":1.2", None),
962
+                RequestOwner::new(":uid.4242", None),
880963
                 Some(ParentWindowContext::X11 { window_id: 42 }),
881964
                 Instant::now(),
882965
             )
@@ -915,7 +998,7 @@ mod tests {
915998
             .requests
916999
             .begin_at(
9171000
                 "req-1",
918
-                RequestOwner::new(":1.2", None),
1001
+                RequestOwner::new(local_sender(), None),
9191002
                 None,
9201003
                 Instant::now(),
9211004
             )
@@ -924,7 +1007,7 @@ mod tests {
9241007
             .requests
9251008
             .transition(
9261009
                 "req-1",
927
-                &RequestOwner::new(":1.2", None),
1010
+                &RequestOwner::new(local_sender(), None),
9281011
                 RequestState::Cancelled,
9291012
             )
9301013
             .expect("first cancel should transition");
@@ -962,7 +1045,7 @@ mod tests {
9621045
             .requests
9631046
             .begin_at(
9641047
                 "req-1",
965
-                RequestOwner::new(":1.2", None),
1048
+                RequestOwner::new(local_sender(), None),
9661049
                 None,
9671050
                 Instant::now(),
9681051
             )
@@ -971,7 +1054,7 @@ mod tests {
9711054
             .requests
9721055
             .transition(
9731056
                 "req-1",
974
-                &RequestOwner::new(":1.2", None),
1057
+                &RequestOwner::new(local_sender(), None),
9751058
                 RequestState::AwaitingUser,
9761059
             )
9771060
             .expect("first awaiting_user should transition");
@@ -1012,7 +1095,7 @@ mod tests {
10121095
             .requests
10131096
             .begin_at(
10141097
                 "req-1",
1015
-                RequestOwner::new(":1.2", Some("org.test.App".to_string())),
1098
+                RequestOwner::new(local_sender(), Some("org.test.App".to_string())),
10161099
                 Some(ParentWindowContext::X11 { window_id: 42 }),
10171100
                 Instant::now(),
10181101
             )
@@ -1021,7 +1104,7 @@ mod tests {
10211104
             .requests
10221105
             .transition(
10231106
                 "req-1",
1024
-                &RequestOwner::new(":1.2", Some("org.test.App".to_string())),
1107
+                &RequestOwner::new(local_sender(), Some("org.test.App".to_string())),
10251108
                 RequestState::AwaitingUser,
10261109
             )
10271110
             .expect("request should transition");
@@ -1038,7 +1121,7 @@ mod tests {
10381121
             ControlResponse::RequestSnapshot {
10391122
                 id: "req-1".to_string(),
10401123
                 state: "awaiting_user".to_string(),
1041
-                sender: ":1.2".to_string(),
1124
+                sender: local_sender(),
10421125
                 app_id: Some("org.test.App".to_string()),
10431126
                 parent_window: Some("x11:0x2a".to_string()),
10441127
             }