diff --git a/src/clone_grant_using_fmap.rs b/src/clone_grant_using_fmap.rs new file mode 100644 index 0000000..11a411c --- /dev/null +++ b/src/clone_grant_using_fmap.rs @@ -0,0 +1,184 @@ +use syscall::data::{Map, Packet}; +use syscall::error::{Error, Result, EFAULT, EINVAL}; +use syscall::flag::{CloneFlags, MapFlags, O_CREAT, O_RDONLY, O_RDWR, O_CLOEXEC, WaitFlags}; +use syscall::scheme::SchemeMut; + +// Start of code copied from syscall. +use std::convert::Infallible; + +use syscall::{ + clone, + close, + EIO, + exit, + pipe2, + read, + write, +}; + +#[must_use = "Daemon::ready must be called"] +pub struct Daemon { + write_pipe: usize, +} + +impl Daemon { + pub fn new Infallible>(f: F) -> Result { + let mut pipes = [0; 2]; + pipe2(&mut pipes, 0)?; + + let [read_pipe, write_pipe] = pipes; + + if unsafe { clone(CloneFlags::empty())? } == 0 { + let _ = close(read_pipe); + + f(Daemon { + write_pipe, + }); + // TODO: Replace Infallible with the never type once it is stabilized. + unreachable!(); + } else { + let _ = close(write_pipe); + + let mut data = [0]; + let res = read(read_pipe, &mut data); + let _ = close(read_pipe); + + if res? == 1 { + //exit(data[0] as usize)?; + //unreachable!(); + Ok(data[0]) + } else { + Err(Error::new(EIO)) + } + } + } + + pub fn ready(self) -> Result<()> { + let res = write(self.write_pipe, &[0]); + let _ = close(self.write_pipe); + + if res? == 1 { + Ok(()) + } else { + Err(Error::new(EIO)) + } + } +} + +// End of code copied from syscall + +struct TestScheme(bool); + +impl SchemeMut for TestScheme { + fn open(&mut self, _path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result { Ok(0) } + fn close(&mut self, _id: usize) -> Result { Ok(0) } + fn fmap(&mut self, id: usize, map: &Map) -> Result { + if map.size != PAGE_SIZE { return Err(Error::new(EINVAL)); } + + let addr = unsafe { syscall::fmap(!0, &Map { offset: 0, size: PAGE_SIZE, flags: MapFlags::MAP_SHARED | MapFlags::PROT_WRITE, address: 0 })? }; + if self.0 { unsafe { (addr as *mut u8).write(42); } } + + Ok(addr) + } +} + +const SCHEME_NAME: &str = "acid_clone_grant_using_fmap"; +const PAGE_SIZE: usize = 4096; + +fn inner(readonly: bool) -> Result<()> { + println!("Testing - {}", if readonly { "readonly" } else { "writable" }); + Daemon::new(move |daemon: Daemon| -> std::convert::Infallible { + let e = |r| { + match r { + Ok(t) => t, + Err(e) => { + eprintln!("error in clone_grant_using_fmap daemon: {}", e); + std::process::exit(1); + } + } + }; + + let socket = e(syscall::open(format!(":{}{}", SCHEME_NAME, readonly), O_CREAT | O_RDWR | O_CLOEXEC)); + + daemon.ready(); + + let mut packet = Packet::default(); + let mut scheme = TestScheme(readonly); + + loop { + if e(syscall::read(socket, &mut packet)) == 0 { break }; + scheme.handle(&mut packet); + if e(syscall::write(socket, &packet)) == 0 { break } + } + let _ = syscall::close(socket); + + std::process::exit(0); + })?; + println!("Started scheme daemon"); + let fd = syscall::open(format!("{}{}:", SCHEME_NAME, readonly), O_CLOEXEC | O_RDONLY)?; + + let ptr = unsafe { syscall::fmap(fd, &Map { offset: 0, size: PAGE_SIZE, flags: MapFlags::MAP_PRIVATE | MapFlags::PROT_READ | MapFlags::PROT_WRITE, address: 0 })? as *mut u8 }; + + println!("Obtained pointer {:p}", ptr); + + // TODO: Prevent optimizations which may cancel out this type of checking. Volatile will most + // likely be adequate. + + if !readonly { + unsafe { + ptr.write_volatile(0x42); + } + } + + let pid; + unsafe { + pid = syscall::clone(CloneFlags::empty())?; + + // Keep in mind these two constants may disappear in the near future. + const PML4_SIZE: usize = 0x0000_0080_0000_0000; + const USER_TMP_GRANT_OFFSET: usize = 7 * PML4_SIZE; + const USER_GRANT_OFFSET: usize = 2 * PML4_SIZE; + + assert!((ptr as usize) >= USER_GRANT_OFFSET); + assert!((ptr as usize) <= USER_TMP_GRANT_OFFSET); + + // Make sure the temporary addresses are not left after the kernel has cloned. + assert_eq!(syscall::virttophys((ptr as usize) - USER_GRANT_OFFSET + USER_TMP_GRANT_OFFSET), Err(Error::new(EFAULT))); + println!("Old memory was correctly unmapped, for the {} process", if pid == 0 { "child" } else { "parent" }); + + if pid == 0 { + println!("Child process: checking..."); + + // We are the child process. Hopefully the kernel copied the grant properly and without + // aliasing. + if readonly { + assert_eq!(ptr.read_volatile(), 42); + } else { + assert_eq!(ptr.read_volatile(), 0); + ptr.write_volatile(0x43); + assert_eq!(ptr.read_volatile(), 0x43); + } + println!("Child process: obtained correct page"); + std::process::exit(0); + } + } + + println!("Waiting..."); + syscall::waitpid(pid, &mut 0, WaitFlags::empty())?; + + unsafe { assert_eq!(ptr.read_volatile(), if readonly { 42 } else { 0x42 }); } + + println!("It worked!"); + + let _ = unsafe { syscall::funmap(ptr as usize, PAGE_SIZE) }; + + syscall::unlink(format!(":{}{}", SCHEME_NAME, readonly))?; + + Ok(()) +} + +pub fn clone_grant_using_fmap() -> Result<(), String> { + inner(false).map_err(|e| e.to_string())?; + inner(true).map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 11e8a3f..984e194 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ //!Acid testing program #![feature(thread_local)] +mod clone_grant_using_fmap; + fn e(error: Result) -> Result { error.map_err(|e| e.to_string()) } @@ -175,6 +177,8 @@ fn tls_test() -> Result<(), String> { Ok(()) } +use self::clone_grant_using_fmap::*; + fn main() { use std::collections::BTreeMap; use std::{env, process}; @@ -187,6 +191,7 @@ fn main() { tests.insert("tcp_fin", tcp_fin_test); tests.insert("thread", thread_test); tests.insert("tls", tls_test); + tests.insert("clone_grant_using_fmap", clone_grant_using_fmap); let mut ran_test = false; for arg in env::args().skip(1) {