// Copyright 2017 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE-BSD-3-Clause file. // SPDX-License-Identifier: BSD-3-Clause /* Copied from the crosvm Project, commit 186eb8b */ //! Wrapper for sending and receiving messages with file descriptors on sockets that accept //! control messages (e.g. Unix domain sockets). use std::fs::File; use std::mem::size_of; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::{UnixDatagram, UnixStream}; use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned}; use crate::errno::{Error, Result}; use libc::{ c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET, }; use std::os::raw::c_int; // Each of the following macros performs the same function as their C counterparts. They are each // macros because they are used to size statically allocated arrays. macro_rules! CMSG_ALIGN { ($len:expr) => { (($len) as usize + size_of::() - 1) & !(size_of::() - 1) }; } macro_rules! CMSG_SPACE { ($len:expr) => { size_of::() + CMSG_ALIGN!($len) }; } // This function (macro in the C version) is not used in any compile time constant slots, so is just // an ordinary function. The returned pointer is hard coded to be RawFd because that's all that this // module supports. #[allow(non_snake_case)] #[inline(always)] fn CMSG_DATA(cmsg_buffer: *mut cmsghdr) -> *mut RawFd { // Essentially returns a pointer to just past the header. cmsg_buffer.wrapping_offset(1) as *mut RawFd } #[cfg(not(target_env = "musl"))] macro_rules! CMSG_LEN { ($len:expr) => { size_of::() + ($len) }; } #[cfg(target_env = "musl")] macro_rules! CMSG_LEN { ($len:expr) => {{ let sz = size_of::() + ($len); assert!(sz <= (std::u32::MAX as usize)); sz as u32 }}; } #[cfg(not(target_env = "musl"))] fn new_msghdr(iovecs: &mut [iovec]) -> msghdr { msghdr { msg_name: null_mut(), msg_namelen: 0, msg_iov: iovecs.as_mut_ptr(), msg_iovlen: iovecs.len(), msg_control: null_mut(), msg_controllen: 0, msg_flags: 0, } } #[cfg(target_env = "musl")] fn new_msghdr(iovecs: &mut [iovec]) -> msghdr { assert!(iovecs.len() <= (std::i32::MAX as usize)); let mut msg: msghdr = unsafe { std::mem::zeroed() }; msg.msg_name = null_mut(); msg.msg_iov = iovecs.as_mut_ptr(); msg.msg_iovlen = iovecs.len() as i32; msg.msg_control = null_mut(); msg } #[cfg(not(target_env = "musl"))] fn set_msg_controllen(msg: &mut msghdr, cmsg_capacity: usize) { msg.msg_controllen = cmsg_capacity; } #[cfg(target_env = "musl")] fn set_msg_controllen(msg: &mut msghdr, cmsg_capacity: usize) { assert!(cmsg_capacity <= (std::u32::MAX as usize)); msg.msg_controllen = cmsg_capacity as u32; } // This function is like CMSG_NEXT, but safer because it reads only from references, although it // does some pointer arithmetic on cmsg_ptr. #[cfg_attr( feature = "cargo-clippy", allow(clippy::cast_ptr_alignment, clippy::unnecessary_cast) )] fn get_next_cmsg(msghdr: &msghdr, cmsg: &cmsghdr, cmsg_ptr: *mut cmsghdr) -> *mut cmsghdr { let next_cmsg = (cmsg_ptr as *mut u8).wrapping_add(CMSG_ALIGN!(cmsg.cmsg_len)) as *mut cmsghdr; if next_cmsg .wrapping_offset(1) .wrapping_sub(msghdr.msg_control as usize) as usize > msghdr.msg_controllen as usize { null_mut() } else { next_cmsg } } const CMSG_BUFFER_INLINE_CAPACITY: usize = CMSG_SPACE!(size_of::() * 32); enum CmsgBuffer { Inline([u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8]), Heap(Box<[cmsghdr]>), } impl CmsgBuffer { fn with_capacity(capacity: usize) -> CmsgBuffer { let cap_in_cmsghdr_units = (capacity.checked_add(size_of::()).unwrap() - 1) / size_of::(); if capacity <= CMSG_BUFFER_INLINE_CAPACITY { CmsgBuffer::Inline([0u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8]) } else { CmsgBuffer::Heap( vec![ cmsghdr { cmsg_len: 0, cmsg_level: 0, cmsg_type: 0, #[cfg(all(target_env = "musl", target_pointer_width = "64"))] __pad1: 0, }; cap_in_cmsghdr_units ] .into_boxed_slice(), ) } } fn as_mut_ptr(&mut self) -> *mut cmsghdr { match self { CmsgBuffer::Inline(a) => a.as_mut_ptr() as *mut cmsghdr, CmsgBuffer::Heap(a) => a.as_mut_ptr(), } } } fn raw_sendmsg(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result { let cmsg_capacity = CMSG_SPACE!(std::mem::size_of_val(out_fds)); let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity); let mut iovecs = Vec::with_capacity(out_data.len()); for data in out_data { iovecs.push(iovec { iov_base: data.as_ptr() as *mut c_void, iov_len: data.size(), }); } let mut msg = new_msghdr(&mut iovecs); if !out_fds.is_empty() { let cmsg = cmsghdr { cmsg_len: CMSG_LEN!(std::mem::size_of_val(out_fds)), cmsg_level: SOL_SOCKET, cmsg_type: SCM_RIGHTS, #[cfg(all(target_env = "musl", target_pointer_width = "64"))] __pad1: 0, }; // SAFETY: Check comments below for each call. unsafe { // Safe because cmsg_buffer was allocated to be large enough to contain cmsghdr. write_unaligned(cmsg_buffer.as_mut_ptr(), cmsg); // Safe because the cmsg_buffer was allocated to be large enough to hold out_fds.len() // file descriptors. copy_nonoverlapping( out_fds.as_ptr(), CMSG_DATA(cmsg_buffer.as_mut_ptr()), out_fds.len(), ); } msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void; set_msg_controllen(&mut msg, cmsg_capacity); } // SAFETY: Safe because the msghdr was properly constructed from valid (or null) pointers of // the indicated length and we check the return value. let write_count = unsafe { sendmsg(fd, &msg, MSG_NOSIGNAL) }; if write_count == -1 { Err(Error::last()) } else { Ok(write_count as usize) } } #[cfg_attr(feature = "cargo-clippy", allow(clippy::unnecessary_cast))] unsafe fn raw_recvmsg( fd: RawFd, iovecs: &mut [iovec], in_fds: &mut [RawFd], ) -> Result<(usize, usize)> { let cmsg_capacity = CMSG_SPACE!(std::mem::size_of_val(in_fds)); let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity); let mut msg = new_msghdr(iovecs); if !in_fds.is_empty() { // MSG control len is size_of(cmsghdr) + size_of(RawFd) * in_fds.len(). msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void; set_msg_controllen(&mut msg, cmsg_capacity); } // Safe because the msghdr was properly constructed from valid (or null) pointers of the // indicated length and we check the return value. // TODO: Should we handle MSG_TRUNC in a specific way? let total_read = recvmsg(fd, &mut msg, 0); if total_read == -1 { return Err(Error::last()); } if total_read == 0 && (msg.msg_controllen as usize) < size_of::() { return Ok((0, 0)); } // Reference to a memory area with a CmsgBuffer, which contains a `cmsghdr` struct followed // by a sequence of `in_fds.len()` count RawFds. let mut cmsg_ptr = msg.msg_control as *mut cmsghdr; let mut copied_fds_count = 0; // If the control data was truncated, then this might be a sign of incorrect communication // protocol. If MSG_CTRUNC was set we must close the fds from the control data. let mut teardown_control_data = msg.msg_flags & libc::MSG_CTRUNC != 0; while !cmsg_ptr.is_null() { // Safe because we checked that cmsg_ptr was non-null, and the loop is constructed such // that it only happens when there is at least sizeof(cmsghdr) space after the pointer to // read. let cmsg = (cmsg_ptr as *mut cmsghdr).read_unaligned(); if cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS { let fds_count: usize = ((cmsg.cmsg_len - CMSG_LEN!(0)) as usize) / size_of::(); // The sender can transmit more data than we can buffer. If a message is too long to // fit in the supplied buffer, excess bytes may be discarded depending on the type of // socket the message is received from. let fds_to_be_copied_count = std::cmp::min(in_fds.len() - copied_fds_count, fds_count); teardown_control_data |= fds_count > fds_to_be_copied_count; if teardown_control_data { // Allocating space for cmesg buffer might provide extra space for fds, due to // alignment. If these fds can not be stored in `in_fds` buffer, then all the control // data must be dropped to insufficient buffer space for returning them to outer // scope. This might be a sign of incorrect protocol communication. for fd_offset in 0..fds_count { let raw_fds_ptr = CMSG_DATA(cmsg_ptr); // The cmsg_ptr is valid here because is checked at the beginning of the // loop and it is assured to have `fds_count` fds available. let raw_fd = *(raw_fds_ptr.wrapping_add(fd_offset)) as c_int; libc::close(raw_fd); } } else { // Safe because `cmsg_ptr` is checked against null and we copy from `cmesg_buffer` to // `in_fds` according to their current capacity. copy_nonoverlapping( CMSG_DATA(cmsg_ptr), in_fds[copied_fds_count..(copied_fds_count + fds_to_be_copied_count)] .as_mut_ptr(), fds_to_be_copied_count, ); copied_fds_count += fds_to_be_copied_count; } } // Remove the previously copied fds. if teardown_control_data { for fd in in_fds.iter().take(copied_fds_count) { // This is safe because we close only the previously copied fds. We do not care // about `close` return code. libc::close(*fd); } return Err(Error::new(libc::ENOBUFS)); } cmsg_ptr = get_next_cmsg(&msg, &cmsg, cmsg_ptr); } Ok((total_read as usize, copied_fds_count)) } /// Trait for file descriptors can send and receive socket control messages via `sendmsg` and /// `recvmsg`. /// /// # Examples /// /// ``` /// # extern crate libc; /// extern crate vmm_sys_util; /// use vmm_sys_util::sock_ctrl_msg::ScmSocket; /// # use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; /// # use std::fs::File; /// # use std::io::Write; /// # use std::os::unix::io::{AsRawFd, FromRawFd}; /// # use std::os::unix::net::UnixDatagram; /// # use std::slice::from_raw_parts; /// /// # use libc::{c_void, iovec}; /// /// let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); /// let evt = EventFd::new(0).expect("failed to create eventfd"); /// /// let write_count = s1 /// .send_with_fds(&[[237].as_ref()], &[evt.as_raw_fd()]) /// .expect("failed to send fd"); /// /// let mut files = [0; 2]; /// let mut buf = [0u8]; /// let mut iovecs = [iovec { /// iov_base: buf.as_mut_ptr() as *mut c_void, /// iov_len: buf.len(), /// }]; /// let (read_count, file_count) = unsafe { /// s2.recv_with_fds(&mut iovecs[..], &mut files) /// .expect("failed to recv fd") /// }; /// /// let mut file = unsafe { File::from_raw_fd(files[0]) }; /// file.write(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) }) /// .expect("failed to write to sent fd"); /// assert_eq!(evt.read().expect("failed to read from eventfd"), 1203); /// ``` pub trait ScmSocket { /// Gets the file descriptor of this socket. fn socket_fd(&self) -> RawFd; /// Sends the given data and file descriptor over the socket. /// /// On success, returns the number of bytes sent. /// /// # Arguments /// /// * `buf` - A buffer of data to send on the `socket`. /// * `fd` - A file descriptors to be sent. fn send_with_fd(&self, buf: D, fd: RawFd) -> Result { self.send_with_fds(&[buf], &[fd]) } /// Sends the given data and file descriptors over the socket. /// /// On success, returns the number of bytes sent. /// /// # Arguments /// /// * `bufs` - A list of data buffer to send on the `socket`. /// * `fds` - A list of file descriptors to be sent. fn send_with_fds(&self, bufs: &[D], fds: &[RawFd]) -> Result { raw_sendmsg(self.socket_fd(), bufs, fds) } /// Receives data and potentially a file descriptor from the socket. /// /// On success, returns the number of bytes and an optional file descriptor. /// /// # Arguments /// /// * `buf` - A buffer to receive data from the socket. fn recv_with_fd(&self, buf: &mut [u8]) -> Result<(usize, Option)> { let mut fd = [0]; let mut iovecs = [iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len(), }]; // SAFETY: Safe because we have mutably borrowed buf and it's safe to write arbitrary data // to a slice. let (read_count, fd_count) = unsafe { self.recv_with_fds(&mut iovecs[..], &mut fd)? }; let file = if fd_count == 0 { None } else { // SAFETY: Safe because the first fd from recv_with_fds is owned by us and valid // because this branch was taken. Some(unsafe { File::from_raw_fd(fd[0]) }) }; Ok((read_count, file)) } /// Receives data and file descriptors from the socket. /// /// On success, returns the number of bytes and file descriptors received as a tuple /// `(bytes count, files count)`. /// /// # Arguments /// /// * `iovecs` - A list of iovec to receive data from the socket. /// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the /// number of valid file descriptors is indicated by the second element of the /// returned tuple. The caller owns these file descriptors, but they will not be /// closed on drop like a `File`-like type would be. It is recommended that each valid /// file descriptor gets wrapped in a drop type that closes it after this returns. /// /// # Safety /// /// It is the callers responsibility to ensure it is safe for arbitrary data to be /// written to the iovec pointers. unsafe fn recv_with_fds( &self, iovecs: &mut [iovec], fds: &mut [RawFd], ) -> Result<(usize, usize)> { raw_recvmsg(self.socket_fd(), iovecs, fds) } } impl ScmSocket for UnixDatagram { fn socket_fd(&self) -> RawFd { self.as_raw_fd() } } impl ScmSocket for UnixStream { fn socket_fd(&self) -> RawFd { self.as_raw_fd() } } /// Trait for types that can be converted into an `iovec` that can be referenced by a syscall for /// the lifetime of this object. /// /// # Safety /// /// This is marked unsafe because the implementation must ensure that the returned pointer and size /// is valid and that the lifetime of the returned pointer is at least that of the trait object. pub unsafe trait IntoIovec { /// Gets the base pointer of this `iovec`. fn as_ptr(&self) -> *const c_void; /// Gets the size in bytes of this `iovec`. fn size(&self) -> usize; } // SAFETY: Safe because this slice can not have another mutable reference and it's pointer and // size are guaranteed to be valid. unsafe impl<'a> IntoIovec for &'a [u8] { // Clippy false positive: https://github.com/rust-lang/rust-clippy/issues/3480 #[cfg_attr(feature = "cargo-clippy", allow(clippy::useless_asref))] fn as_ptr(&self) -> *const c_void { self.as_ref().as_ptr() as *const c_void } fn size(&self) -> usize { self.len() } } #[cfg(test)] mod tests { #![allow(clippy::undocumented_unsafe_blocks)] use super::*; use crate::eventfd::EventFd; use std::io::Write; use std::mem::size_of; use std::os::raw::c_long; use std::os::unix::net::UnixDatagram; use std::slice::from_raw_parts; use libc::cmsghdr; #[test] fn buffer_len() { assert_eq!(CMSG_SPACE!(0), size_of::()); assert_eq!( CMSG_SPACE!(size_of::()), size_of::() + size_of::() ); if size_of::() == 4 { assert_eq!( CMSG_SPACE!(2 * size_of::()), size_of::() + size_of::() ); assert_eq!( CMSG_SPACE!(3 * size_of::()), size_of::() + size_of::() * 2 ); assert_eq!( CMSG_SPACE!(4 * size_of::()), size_of::() + size_of::() * 2 ); } else if size_of::() == 8 { assert_eq!( CMSG_SPACE!(2 * size_of::()), size_of::() + size_of::() * 2 ); assert_eq!( CMSG_SPACE!(3 * size_of::()), size_of::() + size_of::() * 3 ); assert_eq!( CMSG_SPACE!(4 * size_of::()), size_of::() + size_of::() * 4 ); } } #[test] fn send_recv_no_fd() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let write_count = s1 .send_with_fds(&[[1u8, 1, 2].as_ref(), [21u8, 34, 55].as_ref()], &[]) .expect("failed to send data"); assert_eq!(write_count, 6); let mut buf = [0u8; 6]; let mut files = [0; 1]; let mut iovecs = [iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len(), }]; let (read_count, file_count) = unsafe { s2.recv_with_fds(&mut iovecs[..], &mut files) .expect("failed to recv data") }; assert_eq!(read_count, 6); assert_eq!(file_count, 0); assert_eq!(buf, [1, 1, 2, 21, 34, 55]); } #[test] fn send_recv_only_fd() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let evt = EventFd::new(0).expect("failed to create eventfd"); let write_count = s1 .send_with_fd([].as_ref(), evt.as_raw_fd()) .expect("failed to send fd"); assert_eq!(write_count, 0); let (read_count, file_opt) = s2.recv_with_fd(&mut []).expect("failed to recv fd"); let mut file = file_opt.unwrap(); assert_eq!(read_count, 0); assert!(file.as_raw_fd() >= 0); assert_ne!(file.as_raw_fd(), s1.as_raw_fd()); assert_ne!(file.as_raw_fd(), s2.as_raw_fd()); assert_ne!(file.as_raw_fd(), evt.as_raw_fd()); file.write_all(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) }) .expect("failed to write to sent fd"); assert_eq!(evt.read().expect("failed to read from eventfd"), 1203); } #[test] fn send_recv_with_fd() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let evt = EventFd::new(0).expect("failed to create eventfd"); let write_count = s1 .send_with_fds(&[[237].as_ref()], &[evt.as_raw_fd()]) .expect("failed to send fd"); assert_eq!(write_count, 1); let mut files = [0; 2]; let mut buf = [0u8]; let mut iovecs = [iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len(), }]; let (read_count, file_count) = unsafe { s2.recv_with_fds(&mut iovecs[..], &mut files) .expect("failed to recv fd") }; assert_eq!(read_count, 1); assert_eq!(buf[0], 237); assert_eq!(file_count, 1); assert!(files[0] >= 0); assert_ne!(files[0], s1.as_raw_fd()); assert_ne!(files[0], s2.as_raw_fd()); assert_ne!(files[0], evt.as_raw_fd()); let mut file = unsafe { File::from_raw_fd(files[0]) }; file.write_all(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) }) .expect("failed to write to sent fd"); assert_eq!(evt.read().expect("failed to read from eventfd"), 1203); } #[test] // Exercise the code paths that activate the issue of receiving the all the ancillary data, // but missing to provide enough buffer space to store it. fn send_more_recv_less1() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let evt1 = EventFd::new(0).expect("failed to create eventfd"); let evt2 = EventFd::new(0).expect("failed to create eventfd"); let evt3 = EventFd::new(0).expect("failed to create eventfd"); let evt4 = EventFd::new(0).expect("failed to create eventfd"); let write_count = s1 .send_with_fds( &[[237].as_ref()], &[ evt1.as_raw_fd(), evt2.as_raw_fd(), evt3.as_raw_fd(), evt4.as_raw_fd(), ], ) .expect("failed to send fd"); assert_eq!(write_count, 1); let mut files = [0; 2]; let mut buf = [0u8]; let mut iovecs = [iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len(), }]; assert!(unsafe { s2.recv_with_fds(&mut iovecs[..], &mut files).is_err() }); } // Exercise the code paths that activate the issue of receiving part of the sent ancillary // data due to insufficient buffer space, activating `msg_flags` `MSG_CTRUNC` flag. #[test] fn send_more_recv_less2() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let evt1 = EventFd::new(0).expect("failed to create eventfd"); let evt2 = EventFd::new(0).expect("failed to create eventfd"); let evt3 = EventFd::new(0).expect("failed to create eventfd"); let evt4 = EventFd::new(0).expect("failed to create eventfd"); let write_count = s1 .send_with_fds( &[[237].as_ref()], &[ evt1.as_raw_fd(), evt2.as_raw_fd(), evt3.as_raw_fd(), evt4.as_raw_fd(), ], ) .expect("failed to send fd"); assert_eq!(write_count, 1); let mut files = [0; 1]; let mut buf = [0u8]; let mut iovecs = [iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len(), }]; assert!(unsafe { s2.recv_with_fds(&mut iovecs[..], &mut files).is_err() }); } }