1use std::{
2 io, mem,
3 os::fd::{AsRawFd, FromRawFd as _, OwnedFd},
4};
5
6use bytes::BufMut;
7use socket2::{Domain, MaybeUninitSlice, MsgHdrMut, Protocol, SockAddr, SockAddrStorage, SockRef, Socket, Type};
8use tokio::net::UnixDatagram;
9
10use super::ancillary::{ControlMessage, SocketCredentialsAncillaryData};
11use crate::net::addr::{ConnectionAddress, ProcessCredentials, ProcessCredentialsError, ProcessIdentity};
12
13pub fn enable_uds_socket_credentials<'sock, S>(socket: &'sock S) -> io::Result<()>
19where
20 SockRef<'sock>: From<&'sock S>,
21{
22 let sock_ref = SockRef::from(socket);
23 sock_ref.set_passcred(true)
24}
25
26pub(super) fn uds_recvmsg<'sock, S, B: BufMut>(socket: &'sock S, buf: &mut B) -> io::Result<(usize, ConnectionAddress)>
27where
28 SockRef<'sock>: From<&'sock S>,
29{
30 let sock_ref = SockRef::from(socket);
31
32 let sock_storage = SockAddrStorage::zeroed();
38 let sock_storage_len = sock_storage.size_of();
39 let mut sock_addr = unsafe { SockAddr::new(sock_storage, sock_storage_len) };
40
41 let mut ancillary_data = SocketCredentialsAncillaryData::new();
42
43 let data_buf = unsafe { MaybeUninitSlice::new(buf.chunk_mut().as_uninit_slice_mut()) };
44 let mut data_bufs = [data_buf];
45
46 let mut msg_hdr = MsgHdrMut::new()
47 .with_addr(&mut sock_addr)
48 .with_buffers(&mut data_bufs)
49 .with_control(ancillary_data.as_mut_uninit());
50
51 let n = sock_ref.recvmsg(&mut msg_hdr, libc::MSG_CMSG_CLOEXEC)?;
52
53 let control_len = msg_hdr.control_len();
60
61 let process_identity = if control_len > 0 {
62 unsafe {
64 ancillary_data.set_len(control_len);
65 }
66
67 let mut credentials = None;
70
71 for message in unsafe { ancillary_data.messages() } {
73 match message {
74 ControlMessage::Credentials(creds) => {
75 credentials.get_or_insert(creds);
76 }
77 ControlMessage::FileDescriptors(fds) => {
78 for fd in fds {
79 drop(unsafe { OwnedFd::from_raw_fd(fd) });
83 }
84 }
85 }
86 }
87
88 match credentials {
89 Some(creds) => ProcessIdentity::new(creds.pid, creds.uid, creds.gid),
90 None => ProcessIdentity::Error(ProcessCredentialsError::InvalidCredentials),
91 }
92 } else {
93 ProcessIdentity::Unavailable
94 };
95
96 let conn_addr = ConnectionAddress::ProcessLike(process_identity);
97
98 unsafe {
100 buf.advance_mut(n);
101 }
102
103 Ok((n, conn_addr))
104}
105
106pub fn socket_reuseport_supported() -> bool {
108 let socket = match Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)) {
109 Ok(socket) => socket,
110 Err(_) => return false,
111 };
112
113 match socket.set_reuse_port(true) {
114 Ok(()) => true,
115 Err(_) => false,
116 }
117}
118
119pub async fn uds_sendmsg_with_creds(
133 socket: &UnixDatagram, payload: &[u8], credentials: &ProcessCredentials,
134) -> io::Result<usize> {
135 let creds = libc::ucred {
136 pid: credentials.pid as libc::pid_t,
137 uid: credentials.uid,
138 gid: credentials.gid,
139 };
140
141 socket
142 .async_io(tokio::io::Interest::WRITABLE, || {
143 sendmsg_with_ucred(socket.as_raw_fd(), payload, &creds)
144 })
145 .await
146}
147
148fn sendmsg_with_ucred(fd: libc::c_int, payload: &[u8], creds: &libc::ucred) -> io::Result<usize> {
152 let control_len = unsafe { libc::CMSG_SPACE(mem::size_of::<libc::ucred>() as u32) as usize };
155
156 let control_words = control_len.div_ceil(mem::size_of::<usize>());
159 let mut control_buf = vec![0usize; control_words];
160
161 let n = unsafe {
165 let mut iov = libc::iovec {
167 iov_base: payload.as_ptr() as *mut libc::c_void,
168 iov_len: payload.len(),
169 };
170
171 let mut msg: libc::msghdr = mem::zeroed();
172 msg.msg_iov = &mut iov;
173 msg.msg_iovlen = 1;
174 msg.msg_control = control_buf.as_mut_ptr().cast::<libc::c_void>();
175 msg.msg_controllen = control_len as _;
176
177 let cmsg = libc::CMSG_FIRSTHDR(&msg);
179 if cmsg.is_null() {
180 return Err(io::Error::other("failed to obtain cmsghdr from control buffer"));
181 }
182 (*cmsg).cmsg_level = libc::SOL_SOCKET;
183 (*cmsg).cmsg_type = libc::SCM_CREDENTIALS;
184 (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<libc::ucred>() as u32) as _;
185
186 let data_ptr = libc::CMSG_DATA(cmsg) as *mut libc::ucred;
188 std::ptr::write(data_ptr, *creds);
189
190 libc::sendmsg(fd, &msg, libc::MSG_NOSIGNAL)
192 };
193
194 if n < 0 {
195 Err(io::Error::last_os_error())
196 } else {
197 Ok(n as usize)
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use std::{
204 io::Read as _,
205 os::{
206 fd::{AsRawFd, FromRawFd, OwnedFd},
207 unix::net::UnixStream,
208 },
209 };
210
211 use bytes::BytesMut;
212
213 use super::*;
214
215 fn unix_dgram_socketpair() -> (Socket, Socket) {
217 let mut fds: [libc::c_int; 2] = [-1, -1];
218 let rc = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_DGRAM, 0, fds.as_mut_ptr()) };
219 assert_eq!(rc, 0, "socketpair failed: {}", io::Error::last_os_error());
220
221 unsafe { (Socket::from_raw_fd(fds[0]), Socket::from_raw_fd(fds[1])) }
223 }
224
225 #[test]
226 fn uds_recvmsg_reads_peer_credentials() {
227 let (sender, receiver) = unix_dgram_socketpair();
230 enable_uds_socket_credentials(&receiver).expect("enabling SO_PASSCRED should succeed");
231
232 let payload = b"origin-detection-payload";
233 let sent = sender.send(payload).expect("send should succeed");
234 assert_eq!(sent, payload.len());
235
236 let mut buf = BytesMut::with_capacity(128);
237 let (n, addr) = uds_recvmsg(&receiver, &mut buf).expect("recvmsg should succeed");
238 assert_eq!(n, payload.len());
239 assert_eq!(&buf[..], payload);
240
241 let creds = addr
242 .process_credentials()
243 .expect("peer credentials should be present after SO_PASSCRED");
244 assert_eq!(creds.pid, std::process::id() as libc::pid_t);
245 assert_eq!(creds.uid, unsafe { libc::getuid() });
246 assert_eq!(creds.gid, unsafe { libc::getgid() });
247 }
248
249 #[test]
250 fn uds_recvmsg_without_passcred_reports_unavailable() {
251 let (sender, receiver) = unix_dgram_socketpair();
254
255 let payload = b"no-creds-payload";
256 sender.send(payload).expect("send should succeed");
257
258 let mut buf = BytesMut::with_capacity(128);
259 let (n, addr) = uds_recvmsg(&receiver, &mut buf).expect("recvmsg should succeed");
260 assert_eq!(n, payload.len());
261 assert_eq!(&buf[..], payload);
262
263 assert!(matches!(
264 addr,
265 ConnectionAddress::ProcessLike(ProcessIdentity::Unavailable)
266 ));
267 assert!(addr.process_credentials().is_none());
268 }
269
270 fn sendmsg_with_fds(fd: libc::c_int, payload: &[u8], fds: &[libc::c_int]) -> io::Result<usize> {
273 let fds_len = mem::size_of_val(fds);
274 let control_len = unsafe { libc::CMSG_SPACE(fds_len as u32) as usize };
275 let control_words = control_len.div_ceil(mem::size_of::<usize>());
276 let mut control_buf = vec![0usize; control_words];
277
278 let n = unsafe {
280 let mut iov = libc::iovec {
281 iov_base: payload.as_ptr() as *mut libc::c_void,
282 iov_len: payload.len(),
283 };
284
285 let mut msg: libc::msghdr = mem::zeroed();
286 msg.msg_iov = &mut iov;
287 msg.msg_iovlen = 1;
288 msg.msg_control = control_buf.as_mut_ptr().cast::<libc::c_void>();
289 msg.msg_controllen = control_len as _;
290
291 let cmsg = libc::CMSG_FIRSTHDR(&msg);
292 (*cmsg).cmsg_level = libc::SOL_SOCKET;
293 (*cmsg).cmsg_type = libc::SCM_RIGHTS;
294 (*cmsg).cmsg_len = libc::CMSG_LEN(fds_len as u32) as _;
295 std::ptr::copy_nonoverlapping(fds.as_ptr().cast::<u8>(), libc::CMSG_DATA(cmsg), fds_len);
296
297 libc::sendmsg(fd, &msg, libc::MSG_NOSIGNAL)
298 };
299
300 if n < 0 {
301 Err(io::Error::last_os_error())
302 } else {
303 Ok(n as usize)
304 }
305 }
306
307 fn receive_payload_with_fds(enable_credentials: bool) -> ConnectionAddress {
310 let (sender, receiver) = unix_dgram_socketpair();
311 if enable_credentials {
312 enable_uds_socket_credentials(&receiver).expect("enabling SO_PASSCRED should succeed");
313 }
314
315 let (sent_end, probe_end) = UnixStream::pair().expect("stream socketpair should succeed");
321 probe_end.set_nonblocking(true).unwrap();
322
323 let payload = b"origin-detection-payload";
324 let sent =
325 sendmsg_with_fds(sender.as_raw_fd(), payload, &[sent_end.as_raw_fd(); 8]).expect("send should succeed");
326 assert_eq!(sent, payload.len());
327 drop(sent_end);
328
329 let mut buf = BytesMut::with_capacity(128);
330 let (n, addr) = uds_recvmsg(&receiver, &mut buf).expect("recvmsg should succeed");
331 assert_eq!(n, payload.len());
332 assert_eq!(&buf[..], payload);
333
334 let mut probe_buf = [0u8; 1];
335 match (&probe_end).read(&mut probe_buf) {
336 Ok(0) => {}
337 Ok(_) => panic!("unexpected data read from probe socket"),
338 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
339 panic!("file descriptors sent by the peer were left open in our process")
340 }
341 Err(e) => panic!("failed to read from probe socket: {e}"),
342 }
343
344 addr
345 }
346
347 #[test]
348 fn uds_recvmsg_reads_peer_credentials_when_peer_also_sends_file_descriptors() {
349 let addr = receive_payload_with_fds(true);
353
354 let creds = addr
355 .process_credentials()
356 .expect("peer credentials should be present despite truncated ancillary data");
357 assert_eq!(creds.pid, std::process::id() as libc::pid_t);
358 }
359
360 #[test]
361 fn uds_recvmsg_closes_file_descriptors_received_without_credentials() {
362 let addr = receive_payload_with_fds(false);
365
366 assert!(addr.process_credentials().is_none());
367 }
368
369 #[test]
370 fn sendmsg_with_current_credentials_round_trips_payload() {
371 let (sender, receiver) = unsafe {
374 let mut fds: [libc::c_int; 2] = [-1, -1];
375 let rc = libc::socketpair(libc::AF_UNIX, libc::SOCK_DGRAM, 0, fds.as_mut_ptr());
376 assert_eq!(rc, 0, "socketpair failed: {}", io::Error::last_os_error());
377 (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1]))
378 };
379
380 let creds = libc::ucred {
381 pid: std::process::id() as libc::pid_t,
382 uid: unsafe { libc::getuid() },
383 gid: unsafe { libc::getgid() },
384 };
385 let payload = b"uds-sendmsg-test-payload";
386 let written = sendmsg_with_ucred(sender.as_raw_fd(), payload, &creds).expect("send should succeed");
387 assert_eq!(written, payload.len());
388
389 let mut buf = [0u8; 64];
391 let read = unsafe {
392 libc::recv(
393 receiver.as_raw_fd(),
394 buf.as_mut_ptr() as *mut libc::c_void,
395 buf.len(),
396 0,
397 )
398 };
399 assert!(read > 0, "recv failed: {}", io::Error::last_os_error());
400 assert_eq!(&buf[..read as usize], payload);
401 }
402}