Codex Handbook
shell-escalation/src/unix/socket.rs 523 lines
use libc::c_uint;use serde::Deserialize;use serde::Serialize;use socket2::Domain;use socket2::MaybeUninitSlice;use socket2::MsgHdr;use socket2::MsgHdrMut;use socket2::Socket;use socket2::Type;use std::io::IoSlice;use std::mem::MaybeUninit;use std::os::fd::AsRawFd;use std::os::fd::FromRawFd;use std::os::fd::OwnedFd;use std::os::fd::RawFd;use tokio::io::Interest;use tokio::io::unix::AsyncFd;const MAX_FDS_PER_MESSAGE: usize = 16;const LENGTH_PREFIX_SIZE: usize = size_of::<u32>();const MAX_DATAGRAM_SIZE: usize = 8192;/// Converts a slice of MaybeUninit<T> to a slice of T.////// The caller guarantees that every element of `buf` is initialized.fn assume_init<T>(buf: &[MaybeUninit<T>]) -> &[T] {    unsafe { std::slice::from_raw_parts(buf.as_ptr().cast(), buf.len()) }}fn assume_init_slice<T, const N: usize>(buf: &[MaybeUninit<T>; N]) -> &[T; N] {    unsafe { &*(buf as *const [MaybeUninit<T>; N] as *const [T; N]) }}fn assume_init_vec<T>(mut buf: Vec<MaybeUninit<T>>) -> Vec<T> {    unsafe {        let ptr = buf.as_mut_ptr() as *mut T;        let len = buf.len();        let cap = buf.capacity();        std::mem::forget(buf);        Vec::from_raw_parts(ptr, len, cap)    }}fn control_space_for_fds(count: usize) -> usize {    unsafe { libc::CMSG_SPACE((count * size_of::<RawFd>()) as _) as usize }}/// Extracts the FDs from a SCM_RIGHTS control message.fn extract_fds(control: &[u8]) -> Vec<OwnedFd> {    let mut fds = Vec::new();    let mut hdr: libc::msghdr = unsafe { std::mem::zeroed() };    hdr.msg_control = control.as_ptr() as *mut libc::c_void;    hdr.msg_controllen = control.len() as _;    let hdr = hdr; // drop mut    let mut cmsg = unsafe { libc::CMSG_FIRSTHDR(&hdr) as *const libc::cmsghdr };    while !cmsg.is_null() {        let level = unsafe { (*cmsg).cmsg_level };        let ty = unsafe { (*cmsg).cmsg_type };        if level == libc::SOL_SOCKET && ty == libc::SCM_RIGHTS {            let data_ptr = unsafe { libc::CMSG_DATA(cmsg).cast::<RawFd>() };            let fd_count: usize = {                // `cmsghdr::cmsg_len` is not typed consistently across targets, so normalize it                // before doing the size arithmetic.                #[allow(clippy::useless_conversion, clippy::expect_used)]                let cmsg_data_len = usize::try_from(unsafe { (*cmsg).cmsg_len })                    .expect("cmsghdr length fits")                    - unsafe { libc::CMSG_LEN(0) as usize };                cmsg_data_len / size_of::<RawFd>()            };            for i in 0..fd_count {                let fd = unsafe { data_ptr.add(i).read() };                fds.push(unsafe { OwnedFd::from_raw_fd(fd) });            }        }        cmsg = unsafe { libc::CMSG_NXTHDR(&hdr, cmsg) };    }    fds}/// Read a frame from a SOCK_STREAM socket.////// A frame is a message length prefix followed by a payload. FDs may be included in the control/// message when receiving the frame header.async fn read_frame(async_socket: &AsyncFd<Socket>) -> std::io::Result<(Vec<u8>, Vec<OwnedFd>)> {    let (message_len, fds) = read_frame_header(async_socket).await?;    let payload = read_frame_payload(async_socket, message_len).await?;    Ok((payload, fds))}/// Read the frame header (i.e. length) and any FDs from a SOCK_STREAM socket.async fn read_frame_header(    async_socket: &AsyncFd<Socket>,) -> std::io::Result<(usize, Vec<OwnedFd>)> {    let mut header = [MaybeUninit::<u8>::uninit(); LENGTH_PREFIX_SIZE];    let mut filled = 0;    let mut control = vec![MaybeUninit::<u8>::uninit(); control_space_for_fds(MAX_FDS_PER_MESSAGE)];    let mut captured_control = false;    while filled < LENGTH_PREFIX_SIZE {        let mut guard = async_socket.readable().await?;        // The first read should come with a control message containing any FDs.        let read = if !captured_control {            match guard.try_io(|inner| {                let mut bufs = [MaybeUninitSlice::new(&mut header[filled..])];                let (read, control_len) = {                    let mut msg = MsgHdrMut::new()                        .with_buffers(&mut bufs)                        .with_control(&mut control);                    let read = inner.get_ref().recvmsg(&mut msg, 0)?;                    (read, msg.control_len())                };                control.truncate(control_len);                captured_control = true;                Ok(read)            }) {                Ok(Ok(read)) => read,                Ok(Err(err)) => return Err(err),                Err(_would_block) => continue,            }        } else {            match guard.try_io(|inner| inner.get_ref().recv(&mut header[filled..])) {                Ok(Ok(read)) => read,                Ok(Err(err)) => return Err(err),                Err(_would_block) => continue,            }        };        if read == 0 {            return Err(std::io::Error::new(                std::io::ErrorKind::UnexpectedEof,                "socket closed while receiving frame header",            ));        }        filled += read;        assert!(filled <= LENGTH_PREFIX_SIZE);        if filled == LENGTH_PREFIX_SIZE {            let len_bytes = assume_init_slice(&header);            let payload_len = u32::from_le_bytes(*len_bytes) as usize;            let fds = extract_fds(assume_init(&control));            return Ok((payload_len, fds));        }    }    unreachable!("header loop always returns")}/// Read `message_len` bytes from a SOCK_STREAM socket.async fn read_frame_payload(    async_socket: &AsyncFd<Socket>,    message_len: usize,) -> std::io::Result<Vec<u8>> {    if message_len == 0 {        return Ok(Vec::new());    }    let mut payload = vec![MaybeUninit::<u8>::uninit(); message_len];    let mut filled = 0;    while filled < message_len {        let mut guard = async_socket.readable().await?;        let read = match guard.try_io(|inner| inner.get_ref().recv(&mut payload[filled..])) {            Ok(Ok(read)) => read,            Ok(Err(err)) => return Err(err),            Err(_would_block) => continue,        };        if read == 0 {            return Err(std::io::Error::new(                std::io::ErrorKind::UnexpectedEof,                "socket closed while receiving frame payload",            ));        }        filled += read;        assert!(filled <= message_len);        if filled == message_len {            return Ok(assume_init_vec(payload));        }    }    unreachable!("loop exits only after returning payload")}fn send_datagram_bytes(socket: &Socket, data: &[u8], fds: &[OwnedFd]) -> std::io::Result<()> {    let control = make_control_message(fds)?;    let payload = [IoSlice::new(data)];    let msg = if control.is_empty() {        MsgHdr::new().with_buffers(&payload)    } else {        MsgHdr::new().with_buffers(&payload).with_control(&control)    };    let written = socket.sendmsg(&msg, 0)?;    if written != data.len() {        return Err(std::io::Error::new(            std::io::ErrorKind::WriteZero,            format!(                "short datagram write: wrote {written} bytes out of {}",                data.len()            ),        ));    }    Ok(())}fn encode_length(len: usize) -> std::io::Result<[u8; LENGTH_PREFIX_SIZE]> {    let len_u32 = u32::try_from(len).map_err(|_| {        std::io::Error::new(            std::io::ErrorKind::InvalidInput,            format!("message too large: {len}"),        )    })?;    Ok(len_u32.to_le_bytes())}fn make_control_message(fds: &[OwnedFd]) -> std::io::Result<Vec<u8>> {    if fds.len() > MAX_FDS_PER_MESSAGE {        Err(std::io::Error::new(            std::io::ErrorKind::InvalidInput,            format!("too many fds: {}", fds.len()),        ))    } else if fds.is_empty() {        Ok(Vec::new())    } else {        let mut control = vec![0u8; control_space_for_fds(fds.len())];        unsafe {            let cmsg = control.as_mut_ptr().cast::<libc::cmsghdr>();            (*cmsg).cmsg_len =                libc::CMSG_LEN(size_of::<RawFd>() as c_uint * fds.len() as c_uint) as _;            (*cmsg).cmsg_level = libc::SOL_SOCKET;            (*cmsg).cmsg_type = libc::SCM_RIGHTS;            let data_ptr = libc::CMSG_DATA(cmsg).cast::<RawFd>();            for (i, fd) in fds.iter().enumerate() {                data_ptr.add(i).write(fd.as_raw_fd());            }        }        Ok(control)    }}fn receive_datagram_bytes(socket: &Socket) -> std::io::Result<(Vec<u8>, Vec<OwnedFd>)> {    let mut buffer = vec![MaybeUninit::<u8>::uninit(); MAX_DATAGRAM_SIZE];    let mut control = vec![MaybeUninit::<u8>::uninit(); control_space_for_fds(MAX_FDS_PER_MESSAGE)];    let (read, control_len) = {        let mut bufs = [MaybeUninitSlice::new(&mut buffer)];        let mut msg = MsgHdrMut::new()            .with_buffers(&mut bufs)            .with_control(&mut control);        let read = socket.recvmsg(&mut msg, 0)?;        (read, msg.control_len())    };    let data = assume_init(&buffer[..read]).to_vec();    let fds = extract_fds(assume_init(&control[..control_len]));    Ok((data, fds))}pub(crate) struct AsyncSocket {    inner: AsyncFd<Socket>,}impl AsyncSocket {    fn new(socket: Socket) -> std::io::Result<AsyncSocket> {        socket.set_nonblocking(true)?;        let async_socket = AsyncFd::new(socket)?;        Ok(AsyncSocket {            inner: async_socket,        })    }    pub fn from_fd(fd: OwnedFd) -> std::io::Result<AsyncSocket> {        AsyncSocket::new(Socket::from(fd))    }    pub fn pair() -> std::io::Result<(AsyncSocket, AsyncSocket)> {        // `socket2::Socket::pair()` also applies "common flags" (including        // `SO_NOSIGPIPE` on Apple platforms), which can fail for AF_UNIX sockets.        // Use `pair_raw()` to avoid those side effects, then restore `CLOEXEC`        // explicitly on both endpoints.        let (server, client) = Socket::pair_raw(Domain::UNIX, Type::STREAM, None)?;        server.set_cloexec(true)?;        client.set_cloexec(true)?;        Ok((AsyncSocket::new(server)?, AsyncSocket::new(client)?))    }    pub async fn send_with_fds<T: Serialize>(        &self,        msg: T,        fds: &[OwnedFd],    ) -> std::io::Result<()> {        let payload = serde_json::to_vec(&msg)?;        let mut frame = Vec::with_capacity(LENGTH_PREFIX_SIZE + payload.len());        frame.extend_from_slice(&encode_length(payload.len())?);        frame.extend_from_slice(&payload);        send_stream_frame(&self.inner, &frame, fds).await    }    pub async fn receive_with_fds<T: for<'de> Deserialize<'de>>(        &self,    ) -> std::io::Result<(T, Vec<OwnedFd>)> {        let (payload, fds) = read_frame(&self.inner).await?;        let message: T = serde_json::from_slice(&payload)?;        Ok((message, fds))    }    pub async fn send<T>(&self, msg: T) -> std::io::Result<()>    where        T: Serialize,    {        self.send_with_fds(&msg, &[]).await    }    pub async fn receive<T: for<'de> Deserialize<'de>>(&self) -> std::io::Result<T> {        let (msg, fds) = self.receive_with_fds().await?;        if !fds.is_empty() {            tracing::warn!("unexpected fds in receive: {}", fds.len());        }        Ok(msg)    }    pub fn into_inner(self) -> Socket {        self.inner.into_inner()    }}async fn send_stream_frame(    socket: &AsyncFd<Socket>,    frame: &[u8],    fds: &[OwnedFd],) -> std::io::Result<()> {    let mut written = 0;    let mut include_fds = !fds.is_empty();    while written < frame.len() {        let mut guard = socket.writable().await?;        let bytes_written = match guard            .try_io(|inner| send_stream_chunk(inner.get_ref(), &frame[written..], fds, include_fds))        {            Ok(result) => result?,            Err(_would_block) => continue,        };        if bytes_written == 0 {            return Err(std::io::Error::new(                std::io::ErrorKind::WriteZero,                "socket closed while sending frame payload",            ));        }        written += bytes_written;        include_fds = false;    }    Ok(())}fn send_stream_chunk(    socket: &Socket,    frame: &[u8],    fds: &[OwnedFd],    include_fds: bool,) -> std::io::Result<usize> {    let control = if include_fds {        make_control_message(fds)?    } else {        Vec::new()    };    let payload = [IoSlice::new(frame)];    let msg = if control.is_empty() {        MsgHdr::new().with_buffers(&payload)    } else {        MsgHdr::new().with_buffers(&payload).with_control(&control)    };    socket.sendmsg(&msg, 0)}pub(crate) struct AsyncDatagramSocket {    inner: AsyncFd<Socket>,}impl AsyncDatagramSocket {    fn new(socket: Socket) -> std::io::Result<Self> {        socket.set_nonblocking(true)?;        Ok(Self {            inner: AsyncFd::new(socket)?,        })    }    pub unsafe fn from_raw_fd(fd: RawFd) -> std::io::Result<Self> {        Self::new(unsafe { Socket::from_raw_fd(fd) })    }    pub fn pair() -> std::io::Result<(Self, Self)> {        // `socket2::Socket::pair()` also applies "common flags" (including        // `SO_NOSIGPIPE` on Apple platforms), which can fail for AF_UNIX sockets.        // Use `pair_raw()` to avoid those side effects, then restore `CLOEXEC`        // explicitly on both endpoints.        let (server, client) = Socket::pair_raw(Domain::UNIX, Type::DGRAM, None)?;        server.set_cloexec(true)?;        client.set_cloexec(true)?;        Ok((Self::new(server)?, Self::new(client)?))    }    pub async fn send_with_fds(&self, data: &[u8], fds: &[OwnedFd]) -> std::io::Result<()> {        self.inner            .async_io(Interest::WRITABLE, |socket| {                send_datagram_bytes(socket, data, fds)            })            .await    }    pub async fn receive_with_fds(&self) -> std::io::Result<(Vec<u8>, Vec<OwnedFd>)> {        self.inner            .async_io(Interest::READABLE, receive_datagram_bytes)            .await    }    pub fn into_inner(self) -> Socket {        self.inner.into_inner()    }}#[cfg(test)]mod tests {    use super::*;    use pretty_assertions::assert_eq;    use serde::Deserialize;    use serde::Serialize;    use std::os::fd::AsFd;    use std::os::fd::AsRawFd;    use tempfile::NamedTempFile;    #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]    struct TestPayload {        id: i32,        label: String,    }    fn fd_list(count: usize) -> std::io::Result<Vec<OwnedFd>> {        let file = NamedTempFile::new()?;        let mut fds = Vec::new();        for _ in 0..count {            fds.push(file.as_fd().try_clone_to_owned()?);        }        Ok(fds)    }    #[tokio::test]    async fn async_socket_round_trips_payload_and_fds() -> std::io::Result<()> {        let (server, client) = AsyncSocket::pair()?;        let payload = TestPayload {            id: 7,            label: "round-trip".to_string(),        };        let send_fds = fd_list(/*count*/ 1)?;        let receive_task =            tokio::spawn(async move { server.receive_with_fds::<TestPayload>().await });        client.send_with_fds(payload.clone(), &send_fds).await?;        drop(send_fds);        let (received_payload, received_fds) = receive_task.await.unwrap()?;        assert_eq!(payload, received_payload);        assert_eq!(1, received_fds.len());        let fd_status = unsafe { libc::fcntl(received_fds[0].as_raw_fd(), libc::F_GETFD) };        assert!(            fd_status >= 0,            "expected received file descriptor to be valid, but got {fd_status}",        );        Ok(())    }    #[tokio::test]    async fn async_socket_handles_large_payload() -> std::io::Result<()> {        let (server, client) = AsyncSocket::pair()?;        let payload = vec![b'A'; 10_000];        let receive_task = tokio::spawn(async move { server.receive::<Vec<u8>>().await });        client.send(payload.clone()).await?;        let received_payload = receive_task.await.unwrap()?;        assert_eq!(payload, received_payload);        Ok(())    }    #[tokio::test]    async fn async_datagram_sockets_round_trip_messages() -> std::io::Result<()> {        let (server, client) = AsyncDatagramSocket::pair()?;        let data = b"datagram payload".to_vec();        let send_fds = fd_list(/*count*/ 1)?;        let receive_task = tokio::spawn(async move { server.receive_with_fds().await });        client.send_with_fds(&data, &send_fds).await?;        drop(send_fds);        let (received_bytes, received_fds) = receive_task.await.unwrap()?;        assert_eq!(data, received_bytes);        assert_eq!(1, received_fds.len());        Ok(())    }    #[test]    fn send_datagram_bytes_rejects_excessive_fd_counts() -> std::io::Result<()> {        let (socket, _peer) = Socket::pair_raw(Domain::UNIX, Type::DGRAM, None)?;        let fds = fd_list(MAX_FDS_PER_MESSAGE + 1)?;        let err = send_datagram_bytes(&socket, b"hi", &fds).unwrap_err();        assert_eq!(std::io::ErrorKind::InvalidInput, err.kind());        Ok(())    }    #[test]    fn send_stream_chunk_rejects_excessive_fd_counts() -> std::io::Result<()> {        let (socket, _peer) = Socket::pair_raw(Domain::UNIX, Type::STREAM, None)?;        let fds = fd_list(MAX_FDS_PER_MESSAGE + 1)?;        let err = send_stream_chunk(&socket, b"hello", &fds, /*include_fds*/ true).unwrap_err();        assert_eq!(std::io::ErrorKind::InvalidInput, err.kind());        Ok(())    }    #[test]    fn encode_length_errors_for_oversized_messages() {        let err = encode_length(usize::MAX).unwrap_err();        assert_eq!(std::io::ErrorKind::InvalidInput, err.kind());    }    #[tokio::test]    async fn receive_fails_when_peer_closes_before_header() {        let (server, client) = AsyncSocket::pair().expect("failed to create socket pair");        drop(client);        let err = server            .receive::<serde_json::Value>()            .await            .expect_err("expected read failure");        assert_eq!(std::io::ErrorKind::UnexpectedEof, err.kind());    }}