use libredox::protocol::ProcMeta; use std::fmt::Debug; use std::{cmp, convert::TryInto, mem}; use syscall::{error::*, Error}; pub mod dgram; pub mod stream; // TODO: Remove this when Rust libc crate is updated to include SCM_CREDENTIALS const SCM_CREDENTIALS: i32 = 2; const MAX_DGRAM_MSG_LEN: usize = 65536; const MIN_RECV_MSG_LEN: usize = mem::size_of::() * 2; // name_len, payload_len, const CMSG_HEADER_LEN_IN_STREAM: usize = CMSG_LEVEL_SIZE + CMSG_TYPE_SIZE + CMSG_DATA_LEN_SIZE; const CMSG_LEVEL_SIZE: usize = mem::size_of::(); const CMSG_TYPE_SIZE: usize = mem::size_of::(); const CMSG_DATA_LEN_SIZE: usize = mem::size_of::(); const PID_SIZE: usize = mem::size_of::(); const UID_SIZE: usize = mem::size_of::(); const GID_SIZE: usize = mem::size_of::(); #[derive(Debug, Clone, Default, PartialEq, Eq)] struct Credential { pid: i32, uid: i32, gid: i32, } impl Credential { fn new(pid: i32, uid: i32, gid: i32) -> Self { Self { pid, uid, gid } } } #[derive(Debug, Default, Clone, PartialEq, Eq)] struct AncillaryData { cred: Credential, num_fds: usize, name: Option, } impl AncillaryData { fn new(cred: Credential, name: Option) -> Self { Self { cred, name, ..Default::default() } } } struct AncillaryDataHeader { level: i32, c_type: i32, data_len: usize, } impl AncillaryDataHeader { fn from_stream(stream: &[u8]) -> Result> { let mut cursor = 0; if cursor + CMSG_HEADER_LEN_IN_STREAM > stream.len() { return Ok(None); } // cmsg entry format: [level(i32)][type(i32)][data_len(usize)][data] let cmsg_level = read_num::(&stream[cursor..])?; cursor += mem::size_of::(); let cmsg_type = read_num::(&stream[cursor..])?; cursor += mem::size_of::(); let cmsg_data_len = read_num::(&stream[cursor..])?; Ok(Some(Self { level: cmsg_level, c_type: cmsg_type, data_len: cmsg_data_len, })) } } #[derive(Debug, Default, Clone, PartialEq, Eq)] struct DataPacket { payload: Vec, ancillary_data: AncillaryData, // Only for stream packets read_offset: usize, ancillary_taken: bool, } impl DataPacket { fn new(payload: Vec, ancillary_data: AncillaryData) -> Self { Self { payload, ancillary_data, read_offset: 0, ancillary_taken: false, } } fn len(&self) -> usize { self.payload.len() } fn from_stream(stream: &[u8], name: Option, cred: Credential) -> Result { let mut cursor: usize = 0; let payload_len = read_num::(&stream[cursor..])?; cursor += mem::size_of::(); let payload = stream .get(cursor..cursor + payload_len) .ok_or_else(|| { eprintln!("Message::from_stream: Stream too short for payload. Expected len {}, actual remaining len {}", payload_len, stream.len() - cursor); Error::new(EINVAL) })?; cursor += payload_len; // Create a new message with the payload and credentials let mut message = Self::new(payload.to_vec(), AncillaryData::new(cred, name)); while let Some(cmsg_header) = AncillaryDataHeader::from_stream(&stream[cursor..])? { cursor += CMSG_HEADER_LEN_IN_STREAM; let data_stream = stream .get(cursor..cursor + cmsg_header.data_len) .ok_or_else(|| { eprintln!("Message::from_stream: Stream too short for ancillary data. Expected len {}, actual remaining len {}", cmsg_header.data_len, stream.len() - cursor); Error::new(EINVAL) })?; match (cmsg_header.level, cmsg_header.c_type) { (libc::SOL_SOCKET, libc::SCM_RIGHTS) => { // Handle file descriptor passing let num_fds = read_num::(data_stream)?; message.ancillary_data.num_fds += num_fds; } (libc::SOL_SOCKET, SCM_CREDENTIALS) => {} _ => { eprintln!( "Message::from_stream: Unsupported cmsg type received. level: {}, type: {}", cmsg_header.level, cmsg_header.c_type ); return Err(Error::new(EOPNOTSUPP)); } } cursor += cmsg_header.data_len; } Ok(message) } } trait NumFromBytes: Sized + Debug { fn from_le_bytes_slice(buffer: &[u8]) -> Result; } macro_rules! num_from_bytes_impl { ($($t:ty),*) => { $( impl NumFromBytes for $t { fn from_le_bytes_slice(buffer: &[u8]) -> Result { let size = mem::size_of::(); let buffer_slice = buffer.get(..size).and_then(|s| s.try_into().ok()); if let Some(slice) = buffer_slice { Ok(Self::from_le_bytes(slice)) } else { eprintln!( "read_num: buffer is too short to read num of size {} (buffer len: {})", size, buffer.len() ); Err(Error::new(EINVAL)) } } } )* }; } num_from_bytes_impl!(i32, u32, u64, usize); fn read_num(buffer: &[u8]) -> Result where T: NumFromBytes, { T::from_le_bytes_slice(buffer) } fn get_uid_gid_from_pid(cap_fd: usize, target_pid: usize) -> Result<(u32, u32, u32)> { let mut buffer = [0u8; mem::size_of::()]; let _ = libredox::call::get_proc_credentials(cap_fd, target_pid, &mut buffer).map_err(|e| { eprintln!( "Failed to get process credentials for pid {}: {:?}", target_pid, e ); Error::new(EINVAL) })?; let mut cursor = 0; let pid = read_num::(&buffer[cursor..])?; cursor += mem::size_of::() * 3; let uid = read_num::(&buffer[cursor..])?; cursor += mem::size_of::() * 3; let gid = read_num::(&buffer[cursor..])?; Ok((pid, uid, gid)) } fn read_msghdr_info(stream: &mut [u8]) -> Result<(usize, usize, usize)> { if stream.len() < mem::size_of::() * 3 { eprintln!( "get_msghdr_info: stream buffer is too small to read headers. len: {}", stream.len() ); return Err(Error::new(EINVAL)); } let mut cursor: usize = 0; let prepared_name_len = read_num::(&stream[cursor..])?; cursor += mem::size_of::(); let prepared_whole_iov_size = read_num::(&stream[cursor..])?; cursor += mem::size_of::(); let prepared_msg_controllen = read_num::(&stream[cursor..])?; cursor += mem::size_of::(); // Clear the stream buffer stream[..cursor].copy_from_slice(&[0u8; mem::size_of::() * 3]); Ok(( prepared_name_len, prepared_whole_iov_size, prepared_msg_controllen, )) } struct MsgWriter<'a> { buffer: &'a mut [u8], written_len: usize, } impl<'a> MsgWriter<'a> { fn new(buffer: &'a mut [u8]) -> Self { Self { buffer, written_len: 0, } } pub fn len(&self) -> usize { self.written_len } fn write_name( &mut self, name: Option, name_buf_size: usize, name_write_fn: fn(&String, &mut [u8]) -> Result, ) -> Result<()> { if self.buffer.len() < self.written_len + mem::size_of::() { eprintln!("MsgWriter::write_name: Buffer too small to write name length. written_len: {}, buffer_len: {}", self.written_len, self.buffer.len()); self.written_len = self.buffer.len(); return Ok(()); } if let Some(name) = name { let copy_len = cmp::min( name_buf_size, self.buffer.len() - self.written_len - mem::size_of::(), ); if name_buf_size > 0 && copy_len < name.len() { eprintln!("MsgWriter::write_name: Name will be truncated. Full length: {}, buffer available: {}", name.len(), copy_len); } let name_len = name_write_fn( &name, &mut self.buffer[self.written_len + mem::size_of::() ..self.written_len + mem::size_of::() + copy_len], )?; self.buffer[self.written_len..self.written_len + mem::size_of::()] .copy_from_slice(&name_len.to_le_bytes()); self.written_len += mem::size_of::() + name_len; Ok(()) } else { self.written_len += mem::size_of::(); Ok(()) } } fn write_payload(&mut self, payload: &[u8], full_len: usize, iov_size: usize) -> Result { let Some(payload_len_buffer) = self .buffer .get_mut(self.written_len..self.written_len + mem::size_of::()) else { eprintln!("MsgWriter::write_payload: Buffer too small to write payload length. written_len: {}, buffer_len: {}", self.written_len, self.buffer.len()); self.written_len = self.buffer.len(); return Ok(0); }; payload_len_buffer.copy_from_slice(&full_len.to_le_bytes()); self.written_len += mem::size_of::(); let copy_len = cmp::min(iov_size, full_len); if copy_len < full_len { eprintln!("MsgWriter::write_payload: Payload will be truncated. Full length: {}, buffer available: {}", full_len, copy_len); } let Some(payload_buffer) = self .buffer .get_mut(self.written_len..self.written_len + copy_len) else { eprintln!("MsgWriter::write_payload: Buffer too small to write payload data. written_len: {}, buffer_len: {}", self.written_len, self.buffer.len()); self.written_len = self.buffer.len(); return Ok(0); }; payload_buffer.copy_from_slice(&payload[..copy_len]); self.written_len += copy_len; Ok(copy_len) } fn write_cmsg(&mut self, level: i32, c_type: i32, data: &[u8]) -> bool { let data_len = data.len(); let Some(remaining_buf) = self.buffer.get_mut(self.written_len..) else { eprintln!("CmsgWriter::write_cmsg: No remaining buffer space at all."); return false; }; if remaining_buf.len() < CMSG_HEADER_LEN_IN_STREAM { eprintln!("CmsgWriter::write_cmsg: Not enough space for cmsg header. remaining: {}, needed: {}", remaining_buf.len(), CMSG_HEADER_LEN_IN_STREAM); // Fill the remaining buffer with 1s to indicate Imcomplete CMSG header remaining_buf.fill(1); self.written_len += remaining_buf.len(); return false; } let mut cursor = 0; remaining_buf[cursor..cursor + CMSG_LEVEL_SIZE].copy_from_slice(&level.to_le_bytes()); cursor += CMSG_LEVEL_SIZE; remaining_buf[cursor..cursor + CMSG_TYPE_SIZE].copy_from_slice(&c_type.to_le_bytes()); cursor += CMSG_TYPE_SIZE; remaining_buf[cursor..cursor + CMSG_DATA_LEN_SIZE].copy_from_slice(&data_len.to_le_bytes()); cursor += CMSG_DATA_LEN_SIZE; if remaining_buf.len() < cursor + data_len { eprintln!( "CmsgWriter::write_cmsg: Not enough space for cmsg data. remaining: {}, needed: {}", remaining_buf.len(), cursor + data_len ); // Fill the remaining buffer with 1s to indicate Imcomplete CMSG data remaining_buf.fill(1); self.written_len += remaining_buf.len(); return false; } remaining_buf[cursor..cursor + data_len].copy_from_slice(data); self.written_len += cursor + data_len; true } fn write_rights(&mut self, num_fds: usize) -> bool { let data = num_fds.to_le_bytes(); if num_fds == 0 { return true; } self.write_cmsg(libc::SOL_SOCKET, libc::SCM_RIGHTS, &data) } fn write_credentials(&mut self, credential: &Credential) -> bool { let mut data = [0u8; PID_SIZE + UID_SIZE + GID_SIZE]; data[..PID_SIZE].copy_from_slice(&credential.pid.to_le_bytes()); data[PID_SIZE..PID_SIZE + UID_SIZE].copy_from_slice(&credential.uid.to_le_bytes()); data[PID_SIZE + UID_SIZE..PID_SIZE + UID_SIZE + GID_SIZE] .copy_from_slice(&credential.gid.to_le_bytes()); self.write_cmsg(libc::SOL_SOCKET, SCM_CREDENTIALS, &data) } } fn path_buf_to_str(path_buf: &[u8]) -> Result<&str> { match std::str::from_utf8(path_buf) { Ok("") => Err(Error::new(EINVAL)), Ok(s) => Ok(s), Err(_) => Err(Error::new(EINVAL)), } }