nix icon indicating copy to clipboard operation
nix copied to clipboard

possible safety violation via vmsplice

Open jtracey opened this issue 7 months ago • 0 comments
trafficstars

It appears you can read freed memory without unsafe code via vmsplice:

use nix::fcntl::{fcntl, splice, vmsplice, SpliceFFlags};
use nix::unistd::pipe;
use std::error::Error;
use std::fs::File;
use std::io::{IoSlice, Read};
use std::os::fd::{AsFd, AsRawFd};

type E = Box<dyn Error>;

fn vmsplice_all(fd: &(impl AsFd + std::fmt::Debug), buffer: &[u8]) -> Result<(), E> {
    let mut io = IoSlice::new(buffer);
    while !io.is_empty() {
        io.advance(vmsplice(fd, &[io], SpliceFFlags::empty())?);
    }
    Ok(())
}

fn splice_all(fd_in: impl AsFd, fd_out: impl AsFd, mut size: usize) -> Result<(), E> {
    while size > 0 {
        size -= splice(&fd_in, None, &fd_out, None, size, SpliceFFlags::empty())?;
    }
    Ok(())
}

fn main() -> Result<(), E> {
    const BUF_SIZE: usize = 4096 * 3;
    let pipe1 = pipe()?;
    let pipe_sz = fcntl(
        pipe1.1.as_raw_fd(),
        nix::fcntl::FcntlArg::F_SETPIPE_SZ(4 * 4096),
    )?;
    println!("pipe buffer size = {pipe_sz}");
    let pipe2 = pipe()?;
    let stat = vec![1; BUF_SIZE];
    let stat2 = vec![2; BUF_SIZE];
    let mut after = vec![0; BUF_SIZE];
    vmsplice_all(&pipe1.1, &stat)?;
    splice_all(&pipe1.0, &pipe2.1, stat.len())?;
    vmsplice_all(&pipe1.1, &stat2)?;
    drop(stat);
    File::from(pipe2.0).read_exact(&mut after)?;
    println!("{:?}", &after[..20]);
    Ok(())
}

See https://github.com/uutils/coreutils/issues/7625#issuecomment-2816241691 for additional context.

jtracey avatar Apr 18 '25 22:04 jtracey