1use std::fmt;
13use std::io;
14use std::path::PathBuf;
15
16#[derive(Debug, Clone, PartialEq, Eq, Default)]
18#[non_exhaustive]
19pub struct PeerIdentity {
20 pub uid: Option<u32>,
22 pub pid: Option<u32>,
24 pub sid: Option<String>,
26 pub audit_token: Option<[u32; 8]>,
31 pub started: Option<(u64, u64)>,
36}
37
38#[derive(Debug)]
40#[non_exhaustive]
41pub enum PeerError {
42 Unsupported(&'static str),
44 Mismatch(PeerIdentity),
46 Query(io::Error),
48 Unreadable {
55 pid: Option<u32>,
57 image: Option<PathBuf>,
60 kind: PeerFailureKind,
62 source: io::Error,
64 },
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69#[non_exhaustive]
70pub enum PeerFailureKind {
71 Elevated,
75 Other,
77}
78
79impl PeerFailureKind {
80 pub fn as_str(self) -> &'static str {
82 match self {
83 PeerFailureKind::Elevated => "elevated",
84 PeerFailureKind::Other => "other",
85 }
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
92#[non_exhaustive]
93pub struct PeerRefusal {
94 pub pid: Option<u32>,
96 pub image: Option<PathBuf>,
98 pub kind: PeerFailureKind,
100 pub reason: String,
102}
103
104impl fmt::Display for PeerError {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 PeerError::Unsupported(why) => write!(f, "peer authentication unsupported: {why}"),
108 PeerError::Mismatch(id) => {
109 write!(f, "peer is not the current user")?;
110 if let Some(uid) = id.uid {
111 write!(f, " (uid {uid})")?;
112 }
113 if let Some(pid) = id.pid {
114 write!(f, " (pid {pid})")?;
115 }
116 Ok(())
117 }
118 PeerError::Query(e) => write!(f, "could not identify peer: {e}"),
119 PeerError::Unreadable {
120 pid,
121 image,
122 kind,
123 source,
124 } => {
125 write!(f, "could not identify peer")?;
126 if let Some(pid) = pid {
127 write!(f, " (pid {pid})")?;
128 }
129 if let Some(image) = image {
130 write!(f, " ({})", image.display())?;
131 }
132 if *kind == PeerFailureKind::Elevated {
133 write!(
134 f,
135 " (the client runs at a higher integrity level, for example elevated)"
136 )?;
137 }
138 write!(f, ": {source}")
139 }
140 }
141 }
142}
143
144impl std::error::Error for PeerError {
145 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
146 match self {
147 PeerError::Query(e) => Some(e),
148 PeerError::Unreadable { source, .. } => Some(source),
149 _ => None,
150 }
151 }
152}
153
154impl From<PeerError> for io::Error {
155 fn from(e: PeerError) -> Self {
156 let kind = match &e {
157 PeerError::Unsupported(_) => io::ErrorKind::Unsupported,
158 PeerError::Mismatch(_) => io::ErrorKind::PermissionDenied,
159 PeerError::Query(q) => q.kind(),
160 PeerError::Unreadable { source, .. } => source.kind(),
161 };
162 io::Error::new(kind, e.to_string())
163 }
164}
165
166impl PeerError {
167 pub fn pid(&self) -> Option<u32> {
169 match self {
170 PeerError::Mismatch(id) => id.pid,
171 PeerError::Unreadable { pid, .. } => *pid,
172 _ => None,
173 }
174 }
175
176 pub fn image(&self) -> Option<&std::path::Path> {
178 match self {
179 PeerError::Unreadable { image, .. } => image.as_deref(),
180 _ => None,
181 }
182 }
183
184 pub fn failure_kind(&self) -> Option<PeerFailureKind> {
186 match self {
187 PeerError::Unreadable { kind, .. } => Some(*kind),
188 _ => None,
189 }
190 }
191
192 pub(crate) fn refusal(&self) -> Option<PeerRefusal> {
195 match self {
196 PeerError::Unreadable {
197 pid,
198 image,
199 kind,
200 source,
201 } => Some(PeerRefusal {
202 pid: *pid,
203 image: image.clone(),
204 kind: *kind,
205 reason: source.to_string(),
206 }),
207 _ => None,
208 }
209 }
210
211 pub(crate) fn peer_gone(&self) -> bool {
214 let e = match self {
215 PeerError::Query(e) => e,
216 PeerError::Unreadable { source, .. } => source,
217 _ => return false,
218 };
219 if matches!(
220 e.kind(),
221 io::ErrorKind::NotConnected
222 | io::ErrorKind::BrokenPipe
223 | io::ErrorKind::ConnectionReset
224 | io::ErrorKind::UnexpectedEof
225 ) {
226 return true;
227 }
228 cfg!(windows) && matches!(e.raw_os_error(), Some(109 | 232 | 233))
230 }
231}
232
233pub const ACCESS_GENERIC_ALL: u32 = 0x1000_0000;
235pub const ACCESS_PIPE_OWNER_RW: u32 = 0x0012_019b;
239
240#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct AclEntry {
243 pub sid: String,
246 pub mask: u32,
248}
249
250impl AclEntry {
251 pub fn new(sid: impl Into<String>, mask: u32) -> Self {
252 Self {
253 sid: sid.into(),
254 mask,
255 }
256 }
257
258 pub fn full_control(sid: impl Into<String>) -> Self {
260 Self::new(sid, ACCESS_GENERIC_ALL)
261 }
262
263 #[cfg_attr(not(windows), allow(dead_code))]
264 fn valid(&self) -> bool {
265 !self.sid.is_empty()
266 && self.sid.len() <= 184
267 && self
268 .sid
269 .bytes()
270 .all(|b| b.is_ascii_alphanumeric() || b == b'-')
271 }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277#[non_exhaustive]
278pub enum IntegrityLabel {
279 Untrusted,
280 Low,
281 Medium,
282 High,
283}
284
285impl IntegrityLabel {
286 #[cfg_attr(not(windows), allow(dead_code))]
287 fn sid(self) -> &'static str {
288 match self {
289 IntegrityLabel::Untrusted => "S-1-16-0",
290 IntegrityLabel::Low => "S-1-16-4096",
291 IntegrityLabel::Medium => "S-1-16-8192",
292 IntegrityLabel::High => "S-1-16-12288",
293 }
294 }
295}
296
297#[cfg_attr(not(windows), allow(dead_code))]
300pub(crate) fn sddl_for_entries(
301 entries: &[AclEntry],
302 label: Option<IntegrityLabel>,
303) -> Option<String> {
304 if entries.is_empty() || !entries.iter().all(AclEntry::valid) {
305 return None;
306 }
307 let mut out = String::from("D:P");
308 for e in entries {
309 out.push_str(&format!("(A;;0x{:x};;;{})", e.mask, e.sid));
310 }
311 if let Some(label) = label {
312 out.push_str(&format!("S:(ML;;NW;;;{})", label.sid()));
313 }
314 Some(out)
315}
316
317#[cfg(windows)]
320pub(crate) fn sddl_for_sid(sid: &str) -> String {
321 format!("D:P(A;;GA;;;{sid})")
322}
323
324#[cfg(unix)]
329mod unix {
330 use super::*;
331 use std::os::fd::AsRawFd;
332 use std::os::unix::net::UnixStream;
333
334 extern "C" {
335 fn geteuid() -> u32;
336 }
337
338 #[cfg(any(
339 target_os = "macos",
340 target_os = "ios",
341 target_os = "freebsd",
342 target_os = "openbsd",
343 target_os = "netbsd",
344 target_os = "dragonfly"
345 ))]
346 extern "C" {
347 fn getpeereid(fd: i32, euid: *mut u32, egid: *mut u32) -> i32;
348 }
349
350 #[cfg(all(
354 any(target_os = "linux", target_os = "android"),
355 not(any(
356 target_arch = "mips",
357 target_arch = "mips32r6",
358 target_arch = "mips64",
359 target_arch = "mips64r6",
360 target_arch = "powerpc",
361 target_arch = "powerpc64",
362 target_arch = "sparc",
363 target_arch = "sparc64"
364 ))
365 ))]
366 mod linux {
367 pub const SOL_SOCKET: i32 = 1;
368 pub const SO_PEERCRED: i32 = 17;
369 #[repr(C)]
370 #[derive(Default)]
371 pub struct Ucred {
372 pub pid: i32,
373 pub uid: u32,
374 pub gid: u32,
375 }
376 extern "C" {
377 pub fn getsockopt(
378 fd: i32,
379 level: i32,
380 name: i32,
381 val: *mut core::ffi::c_void,
382 len: *mut u32,
383 ) -> i32;
384 }
385 }
386
387 pub(crate) fn current_euid() -> u32 {
388 unsafe { geteuid() }
389 }
390
391 #[cfg(any(
392 target_os = "macos",
393 target_os = "ios",
394 target_os = "freebsd",
395 target_os = "openbsd",
396 target_os = "netbsd",
397 target_os = "dragonfly"
398 ))]
399 fn peer_identity(s: &UnixStream) -> Result<PeerIdentity, PeerError> {
400 let (mut uid, mut gid) = (u32::MAX, u32::MAX);
401 let rc = unsafe { getpeereid(s.as_raw_fd(), &mut uid, &mut gid) };
403 if rc != 0 {
404 return Err(PeerError::Query(io::Error::last_os_error()));
405 }
406 Ok(PeerIdentity {
407 uid: Some(uid),
408 pid: peer_pid(s),
409 sid: None,
410 audit_token: crate::host_identity::peer_audit_token(s),
411 started: peer_pid(s).and_then(crate::host_identity::process_start_marker),
412 })
413 }
414
415 #[cfg(any(target_os = "macos", target_os = "ios"))]
418 fn peer_pid(s: &UnixStream) -> Option<u32> {
419 const SOL_LOCAL: i32 = 0;
420 const LOCAL_PEERPID: i32 = 0x002;
421 extern "C" {
422 fn getsockopt(
423 fd: i32,
424 level: i32,
425 name: i32,
426 val: *mut core::ffi::c_void,
427 len: *mut u32,
428 ) -> i32;
429 }
430 let mut pid: i32 = 0;
431 let mut len = std::mem::size_of::<i32>() as u32;
432 let rc = unsafe {
435 getsockopt(
436 s.as_raw_fd(),
437 SOL_LOCAL,
438 LOCAL_PEERPID,
439 (&mut pid as *mut i32).cast(),
440 &mut len,
441 )
442 };
443 if rc != 0 {
444 return None;
445 }
446 u32::try_from(pid).ok().filter(|p| *p != 0)
447 }
448
449 #[cfg(any(
451 target_os = "freebsd",
452 target_os = "openbsd",
453 target_os = "netbsd",
454 target_os = "dragonfly"
455 ))]
456 fn peer_pid(_s: &UnixStream) -> Option<u32> {
457 None
458 }
459
460 #[cfg(all(
461 any(target_os = "linux", target_os = "android"),
462 not(any(
463 target_arch = "mips",
464 target_arch = "mips32r6",
465 target_arch = "mips64",
466 target_arch = "mips64r6",
467 target_arch = "powerpc",
468 target_arch = "powerpc64",
469 target_arch = "sparc",
470 target_arch = "sparc64"
471 ))
472 ))]
473 fn peer_identity(s: &UnixStream) -> Result<PeerIdentity, PeerError> {
474 let mut cred = linux::Ucred::default();
475 let mut len = std::mem::size_of::<linux::Ucred>() as u32;
476 let rc = unsafe {
478 linux::getsockopt(
479 s.as_raw_fd(),
480 linux::SOL_SOCKET,
481 linux::SO_PEERCRED,
482 &mut cred as *mut linux::Ucred as *mut core::ffi::c_void,
483 &mut len,
484 )
485 };
486 if rc != 0 {
487 return Err(PeerError::Query(io::Error::last_os_error()));
488 }
489 if (len as usize) < std::mem::size_of::<linux::Ucred>() {
490 return Err(PeerError::Query(io::Error::new(
491 io::ErrorKind::InvalidData,
492 "short SO_PEERCRED reply",
493 )));
494 }
495 Ok(PeerIdentity {
496 uid: Some(cred.uid),
497 pid: u32::try_from(cred.pid).ok().filter(|p| *p != 0),
498 sid: None,
499 audit_token: None,
500 started: u32::try_from(cred.pid)
501 .ok()
502 .filter(|p| *p != 0)
503 .and_then(crate::host_identity::process_start_marker),
504 })
505 }
506
507 #[cfg(not(any(
508 target_os = "macos",
509 target_os = "ios",
510 target_os = "freebsd",
511 target_os = "openbsd",
512 target_os = "netbsd",
513 target_os = "dragonfly",
514 all(
515 any(target_os = "linux", target_os = "android"),
516 not(any(
517 target_arch = "mips",
518 target_arch = "mips32r6",
519 target_arch = "mips64",
520 target_arch = "mips64r6",
521 target_arch = "powerpc",
522 target_arch = "powerpc64",
523 target_arch = "sparc",
524 target_arch = "sparc64"
525 ))
526 )
527 )))]
528 fn peer_identity(_s: &UnixStream) -> Result<PeerIdentity, PeerError> {
529 Err(PeerError::Unsupported(
530 "no peer-credential query is implemented for this Unix platform",
531 ))
532 }
533
534 pub(crate) fn accept_uid(id: PeerIdentity, me: u32) -> Result<PeerIdentity, PeerError> {
536 if id.uid == Some(me) {
537 Ok(id)
538 } else {
539 Err(PeerError::Mismatch(id))
540 }
541 }
542
543 pub fn verify_peer_is_current_user(stream: &UnixStream) -> Result<PeerIdentity, PeerError> {
553 accept_uid(peer_identity(stream)?, current_euid())
554 }
555}
556
557#[cfg(unix)]
558pub(crate) use unix::current_euid;
559#[cfg(unix)]
560pub use unix::verify_peer_is_current_user;
561
562#[cfg(windows)]
567mod win {
568 use super::*;
569 use std::os::windows::ffi::OsStringExt;
570 use std::os::windows::io::RawHandle;
571 use windows::core::PWSTR;
572 use windows::Win32::Foundation::{CloseHandle, LocalFree, ERROR_ACCESS_DENIED, HANDLE, HLOCAL};
573 use windows::Win32::Security::Authorization::{
574 ConvertSidToStringSidW, ConvertStringSecurityDescriptorToSecurityDescriptorW,
575 SDDL_REVISION_1,
576 };
577 use windows::Win32::Security::{
578 GetTokenInformation, TokenUser, PSECURITY_DESCRIPTOR, PSID, SECURITY_ATTRIBUTES,
579 TOKEN_QUERY, TOKEN_USER,
580 };
581 use windows::Win32::System::Pipes::{GetNamedPipeClientProcessId, GetNamedPipeServerProcessId};
582 use windows::Win32::System::Threading::{
583 GetCurrentProcess, OpenProcess, OpenProcessToken, QueryFullProcessImageNameW,
584 PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION,
585 };
586
587 fn win_err(e: windows::core::Error) -> io::Error {
589 io::Error::from_raw_os_error((e.code().0 as u32 & 0xFFFF) as i32)
590 }
591
592 struct Owned(HANDLE);
593 impl Drop for Owned {
594 fn drop(&mut self) {
595 unsafe {
596 let _ = CloseHandle(self.0);
597 }
598 }
599 }
600
601 fn sid_to_string(sid: PSID) -> io::Result<String> {
602 unsafe {
603 let mut p = PWSTR::null();
604 ConvertSidToStringSidW(sid, &mut p).map_err(win_err)?;
605 let s = p.to_string().map_err(io::Error::other);
606 let _ = LocalFree(Some(HLOCAL(p.0 as *mut _)));
607 s
608 }
609 }
610
611 fn token_user_sid(tok: HANDLE) -> io::Result<String> {
613 unsafe {
614 let mut need = 0u32;
615 let _ = GetTokenInformation(tok, TokenUser, None, 0, &mut need);
616 if need == 0 {
617 return Err(io::Error::other("token user size query returned 0"));
618 }
619 let mut buf = vec![0u64; (need as usize).div_ceil(8)];
621 GetTokenInformation(
622 tok,
623 TokenUser,
624 Some(buf.as_mut_ptr() as *mut _),
625 (buf.len() * 8) as u32,
626 &mut need,
627 )
628 .map_err(win_err)?;
629 let user = &*(buf.as_ptr() as *const TOKEN_USER);
630 sid_to_string(user.User.Sid)
631 }
632 }
633
634 pub(crate) fn current_user_sid_string() -> io::Result<String> {
636 unsafe {
637 let mut tok = HANDLE::default();
638 OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut tok).map_err(win_err)?;
639 let tok = Owned(tok);
640 token_user_sid(tok.0)
641 }
642 }
643
644 fn process_image(proc: HANDLE) -> Option<PathBuf> {
646 let mut buf = vec![0u16; 32768];
647 let mut len = buf.len() as u32;
648 unsafe {
650 QueryFullProcessImageNameW(proc, PROCESS_NAME_WIN32, PWSTR(buf.as_mut_ptr()), &mut len)
651 }
652 .ok()?;
653 (len > 0).then(|| PathBuf::from(std::ffi::OsString::from_wide(&buf[..len as usize])))
654 }
655
656 fn is_access_denied(e: &io::Error) -> bool {
658 e.raw_os_error() == Some(ERROR_ACCESS_DENIED.0 as i32)
659 }
660
661 fn process_user_sid_string(pid: u32) -> Result<String, PeerError> {
666 unsafe {
667 let proc = Owned(
668 OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
669 .map_err(|e| unreadable(pid, None, win_err(e)))?,
670 );
671 let mut tok = HANDLE::default();
672 if let Err(e) = OpenProcessToken(proc.0, TOKEN_QUERY, &mut tok) {
673 return Err(unreadable(pid, process_image(proc.0), win_err(e)));
674 }
675 let tok = Owned(tok);
676 token_user_sid(tok.0).map_err(|e| unreadable(pid, process_image(proc.0), e))
677 }
678 }
679
680 fn unreadable(pid: u32, image: Option<PathBuf>, source: io::Error) -> PeerError {
681 let kind = if is_access_denied(&source) {
682 PeerFailureKind::Elevated
683 } else {
684 PeerFailureKind::Other
685 };
686 PeerError::Unreadable {
687 pid: Some(pid),
688 image,
689 kind,
690 source,
691 }
692 }
693
694 fn verify_pid_against(pid: u32, expected: &str) -> Result<PeerIdentity, PeerError> {
696 if pid == 0 {
697 return Err(PeerError::Query(io::Error::other("peer process id is 0")));
698 }
699 let theirs = process_user_sid_string(pid)?;
700 let id = PeerIdentity {
701 uid: None,
702 pid: Some(pid),
703 sid: Some(theirs.clone()),
704 audit_token: None,
705 started: crate::host_identity::process_start_marker(pid),
706 };
707 if theirs.eq_ignore_ascii_case(expected) {
710 Ok(id)
711 } else {
712 Err(PeerError::Mismatch(id))
713 }
714 }
715
716 fn verify_pid(pid: u32) -> Result<PeerIdentity, PeerError> {
718 let me = current_user_sid_string().map_err(PeerError::Query)?;
719 verify_pid_against(pid, &me)
720 }
721
722 pub fn verify_pipe_peer_is_current_user(handle: RawHandle) -> Result<PeerIdentity, PeerError> {
734 let mut pid = 0u32;
735 unsafe { GetNamedPipeClientProcessId(HANDLE(handle), &mut pid) }
737 .map_err(|e| PeerError::Query(win_err(e)))?;
738 verify_pid(pid)
739 }
740
741 pub fn verify_pipe_peer_is_sid(
750 handle: RawHandle,
751 sid: &str,
752 ) -> Result<PeerIdentity, PeerError> {
753 if sid.is_empty() {
754 return Err(PeerError::Query(io::Error::other(
755 "no SID to compare the peer with",
756 )));
757 }
758 let mut pid = 0u32;
759 unsafe { GetNamedPipeClientProcessId(HANDLE(handle), &mut pid) }
761 .map_err(|e| PeerError::Query(win_err(e)))?;
762 verify_pid_against(pid, sid)
763 }
764
765 pub fn verify_pipe_server_is_current_user(
771 handle: RawHandle,
772 ) -> Result<PeerIdentity, PeerError> {
773 let mut pid = 0u32;
774 unsafe { GetNamedPipeServerProcessId(HANDLE(handle), &mut pid) }
776 .map_err(|e| PeerError::Query(win_err(e)))?;
777 verify_pid(pid)
778 }
779
780 pub struct OwnerOnlySecurityAttributes {
797 attrs: Box<SECURITY_ATTRIBUTES>,
798 sd: PSECURITY_DESCRIPTOR,
799 sddl: String,
800 }
801
802 unsafe impl Send for OwnerOnlySecurityAttributes {}
805 unsafe impl Sync for OwnerOnlySecurityAttributes {}
806
807 impl OwnerOnlySecurityAttributes {
808 pub fn as_ptr(&self) -> *const core::ffi::c_void {
813 &*self.attrs as *const SECURITY_ATTRIBUTES as *const core::ffi::c_void
814 }
815
816 pub fn sddl(&self) -> &str {
818 &self.sddl
819 }
820
821 pub fn as_raw(&self) -> &SECURITY_ATTRIBUTES {
824 &self.attrs
825 }
826
827 pub(crate) fn raw(&self) -> &SECURITY_ATTRIBUTES {
828 &self.attrs
829 }
830 }
831
832 impl Drop for OwnerOnlySecurityAttributes {
833 fn drop(&mut self) {
834 unsafe {
835 let _ = LocalFree(Some(HLOCAL(self.sd.0)));
836 }
837 }
838 }
839
840 pub fn owner_only_security_attributes() -> io::Result<OwnerOnlySecurityAttributes> {
843 let sddl = sddl_for_sid(¤t_user_sid_string()?);
844 attributes_from_sddl(sddl)
845 }
846
847 pub fn owner_only_security_attributes_with(
852 extra: &[AclEntry],
853 label: Option<IntegrityLabel>,
854 ) -> io::Result<OwnerOnlySecurityAttributes> {
855 let mut entries = vec![AclEntry::full_control(current_user_sid_string()?)];
856 entries.extend(extra.iter().cloned());
857 pipe_security_attributes(&entries, label)
858 }
859
860 pub fn pipe_security_attributes(
867 entries: &[AclEntry],
868 label: Option<IntegrityLabel>,
869 ) -> io::Result<OwnerOnlySecurityAttributes> {
870 let sddl = sddl_for_entries(entries, label).ok_or_else(|| {
871 io::Error::new(
872 io::ErrorKind::InvalidInput,
873 "a pipe DACL needs at least one entry and plain SID strings",
874 )
875 })?;
876 attributes_from_sddl(sddl)
877 }
878
879 fn attributes_from_sddl(sddl: String) -> io::Result<OwnerOnlySecurityAttributes> {
880 let wide: Vec<u16> = sddl.encode_utf16().chain(std::iter::once(0)).collect();
881 let mut sd = PSECURITY_DESCRIPTOR::default();
882 unsafe {
883 ConvertStringSecurityDescriptorToSecurityDescriptorW(
884 windows::core::PCWSTR(wide.as_ptr()),
885 SDDL_REVISION_1,
886 &mut sd,
887 None,
888 )
889 .map_err(win_err)?;
890 }
891 let attrs = Box::new(SECURITY_ATTRIBUTES {
892 nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
893 lpSecurityDescriptor: sd.0,
894 bInheritHandle: false.into(),
895 });
896 Ok(OwnerOnlySecurityAttributes { attrs, sd, sddl })
897 }
898}
899
900#[cfg(windows)]
901pub use win::{
902 owner_only_security_attributes, owner_only_security_attributes_with, pipe_security_attributes,
903 verify_pipe_peer_is_current_user, verify_pipe_peer_is_sid, verify_pipe_server_is_current_user,
904 OwnerOnlySecurityAttributes,
905};
906
907#[cfg(windows)]
911pub fn current_user_sid() -> io::Result<String> {
912 win::current_user_sid_string()
913}