diff --git a/crates/wasi-c2/src/ctx.rs b/crates/wasi-c2/src/ctx.rs index e7156f12a5..7295382dfa 100644 --- a/crates/wasi-c2/src/ctx.rs +++ b/crates/wasi-c2/src/ctx.rs @@ -26,36 +26,23 @@ impl WasiCtx { } } - pub fn insert_file( - &self, - fd: u32, - file: Box, - base_caps: FileCaps, - inheriting_caps: FileCaps, - ) { - let e = FileEntry { - base_caps, - inheriting_caps, - file, - }; - self.table().insert_at(fd, Box::new(e)); + pub fn insert_file(&self, fd: u32, file: Box, caps: FileCaps) { + self.table() + .insert_at(fd, Box::new(FileEntry::new(caps, file))); } pub fn insert_dir( &self, fd: u32, dir: Box, - base_caps: DirCaps, - inheriting_caps: DirCaps, + caps: DirCaps, + file_caps: FileCaps, path: PathBuf, ) { - let e = DirEntry { - base_caps, - inheriting_caps, - preopen_path: Some(path), - dir, - }; - self.table().insert_at(fd, Box::new(e)); + self.table().insert_at( + fd, + Box::new(DirEntry::new(caps, file_caps, Some(path), dir)), + ); } pub fn table(&self) -> RefMut { @@ -79,8 +66,7 @@ impl WasiCtxBuilder { self.0.insert_file( 0, f, - FileCaps::READ, // XXX probably more rights are ok - FileCaps::READ, + FileCaps::READ, // XXX fixme: more rights are ok, but this is read-only ); self } @@ -89,8 +75,7 @@ impl WasiCtxBuilder { self.0.insert_file( 1, f, - FileCaps::WRITE, // XXX probably more rights are ok - FileCaps::WRITE, + FileCaps::WRITE, // XXX fixme: more rights are ok, but this is append only ); self } @@ -99,8 +84,7 @@ impl WasiCtxBuilder { self.0.insert_file( 2, f, - FileCaps::WRITE, // XXX probably more rights are ok - FileCaps::WRITE, + FileCaps::WRITE, // XXX fixme: more rights are ok, but this is append only ); self } @@ -116,14 +100,14 @@ impl WasiCtxBuilder { dir: Box, path: impl AsRef, ) -> Result<&mut Self, Error> { - let base_caps = DirCaps::OPEN; - let inheriting_caps = DirCaps::OPEN; - self.0.table().push(Box::new(DirEntry { - base_caps, - inheriting_caps, - preopen_path: Some(path.as_ref().to_owned()), + let caps = DirCaps::OPEN | DirCaps::CREATE_FILE; // XXX more base caps + let file_caps = FileCaps::READ | FileCaps::WRITE; // XXX more base caps + self.0.table().push(Box::new(DirEntry::new( + caps, + file_caps, + Some(path.as_ref().to_owned()), dir, - }))?; + )))?; Ok(self) } } diff --git a/crates/wasi-c2/src/dir.rs b/crates/wasi-c2/src/dir.rs index 9a3b9e6403..93334bc390 100644 --- a/crates/wasi-c2/src/dir.rs +++ b/crates/wasi-c2/src/dir.rs @@ -4,7 +4,6 @@ use crate::error::Error; use crate::file::{FileCaps, OFlags, WasiFile}; use std::ops::Deref; use std::path::{Path, PathBuf}; -use tracing::debug; pub trait WasiDir { fn open_file( @@ -24,27 +23,60 @@ pub trait WasiDir { } pub(crate) struct DirEntry { - pub(crate) base_caps: DirCaps, - pub(crate) inheriting_caps: DirCaps, - pub(crate) preopen_path: Option, // precondition: PathBuf is valid unicode - pub(crate) dir: Box, + caps: DirCaps, + file_caps: FileCaps, + preopen_path: Option, // precondition: PathBuf is valid unicode + dir: Box, } impl DirEntry { - pub fn get_cap(&self, caps: DirCaps) -> Result<&dyn WasiDir, Error> { - if self.base_caps.contains(&caps) && self.inheriting_caps.contains(&caps) { - Ok(self.dir.deref()) - } else { - Err(Error::DirNotCapable(caps)) + pub fn new( + caps: DirCaps, + file_caps: FileCaps, + preopen_path: Option, + dir: Box, + ) -> Self { + DirEntry { + caps, + file_caps, + preopen_path, + dir, } } - + pub fn get_cap(&self, caps: DirCaps) -> Result<&dyn WasiDir, Error> { + if self.caps.contains(&caps) { + Ok(self.dir.deref()) + } else { + Err(Error::DirNotCapable { + desired: caps, + has: self.caps, + }) + } + } + pub fn drop_caps_to(&mut self, caps: DirCaps, file_caps: FileCaps) -> Result<(), Error> { + if self.caps.contains(&caps) && self.file_caps.contains(&file_caps) { + self.caps = caps; + self.file_caps = file_caps; + Ok(()) + } else { + Err(Error::NotCapable) + } + } + pub fn child_dir_caps(&self, desired_caps: DirCaps) -> DirCaps { + self.caps.intersection(&desired_caps) + } + pub fn child_file_caps(&self, desired_caps: FileCaps) -> FileCaps { + self.file_caps.intersection(&desired_caps) + } pub fn get_dirstat(&self) -> DirStat { DirStat { - base_caps: self.base_caps, - inheriting_caps: self.inheriting_caps, + dir_caps: self.caps, + file_caps: self.file_caps, } } + pub fn preopen_path(&self) -> &Option { + &self.preopen_path + } } #[derive(Debug, Clone, Copy)] @@ -62,6 +94,12 @@ impl DirCaps { self.flags & other.flags == other.flags } + /// Intersection of two sets of flags (bitwise and) + pub fn intersection(&self, rhs: &Self) -> Self { + DirCaps { + flags: self.flags & rhs.flags, + } + } pub const CREATE_DIRECTORY: Self = DirCaps { flags: 1 }; pub const CREATE_FILE: Self = DirCaps { flags: 2 }; pub const LINK_SOURCE: Self = DirCaps { flags: 4 }; @@ -86,8 +124,8 @@ impl std::ops::BitOr for DirCaps { } pub struct DirStat { - pub base_caps: DirCaps, - pub inheriting_caps: DirCaps, + pub file_caps: FileCaps, + pub dir_caps: DirCaps, } impl std::fmt::Display for DirCaps { diff --git a/crates/wasi-c2/src/error.rs b/crates/wasi-c2/src/error.rs index bb63ae6db0..8c278eb7ef 100644 --- a/crates/wasi-c2/src/error.rs +++ b/crates/wasi-c2/src/error.rs @@ -17,12 +17,12 @@ pub enum Error { GetRandom(#[from] getrandom::Error), /// Errno::Notcapable: Extension: Capabilities insufficient - #[error("File not capable: {0}")] - FileNotCapable(FileCaps), + #[error("File not capable: desired {desired}, has {has}")] + FileNotCapable { desired: FileCaps, has: FileCaps }, /// Errno::Notcapable: Extension: Capabilities insufficient - #[error("Directory not capable: {0}")] - DirNotCapable(DirCaps), + #[error("Directory not capable: desired {desired}, has {has}")] + DirNotCapable { desired: DirCaps, has: DirCaps }, /// Idk what the deal with this guy is yet #[error("Table overflow")] diff --git a/crates/wasi-c2/src/file.rs b/crates/wasi-c2/src/file.rs index 476fa01995..9674650630 100644 --- a/crates/wasi-c2/src/file.rs +++ b/crates/wasi-c2/src/file.rs @@ -98,25 +98,39 @@ pub struct Filestat { } pub(crate) struct FileEntry { - pub(crate) base_caps: FileCaps, - pub(crate) inheriting_caps: FileCaps, - pub(crate) file: Box, + caps: FileCaps, + file: Box, } impl FileEntry { + pub fn new(caps: FileCaps, file: Box) -> Self { + FileEntry { caps, file } + } + pub fn get_cap(&self, caps: FileCaps) -> Result<&dyn WasiFile, Error> { - if self.base_caps.contains(&caps) && self.inheriting_caps.contains(&caps) { + if self.caps.contains(&caps) { Ok(self.file.deref()) } else { - Err(Error::FileNotCapable(caps)) + Err(Error::FileNotCapable { + desired: caps, + has: self.caps, + }) + } + } + + pub fn drop_caps_to(&mut self, caps: FileCaps) -> Result<(), Error> { + if self.caps.contains(&caps) { + self.caps = caps; + Ok(()) + } else { + Err(Error::NotCapable) } } pub fn get_fdstat(&self) -> Result { Ok(FdStat { filetype: self.file.get_filetype()?, - base_caps: self.base_caps, - inheriting_caps: self.inheriting_caps, + caps: self.caps, flags: self.file.get_fdflags()?, }) } @@ -137,6 +151,13 @@ impl FileCaps { self.flags & other.flags == other.flags } + /// Intersection of two sets of flags (bitwise and) + pub fn intersection(&self, rhs: &Self) -> Self { + FileCaps { + flags: self.flags & rhs.flags, + } + } + pub const DATASYNC: Self = FileCaps { flags: 1 }; pub const READ: Self = FileCaps { flags: 2 }; pub const SEEK: Self = FileCaps { flags: 4 }; @@ -168,8 +189,7 @@ impl std::fmt::Display for FileCaps { pub struct FdStat { pub filetype: Filetype, - pub base_caps: FileCaps, - pub inheriting_caps: FileCaps, + pub caps: FileCaps, pub flags: FdFlags, } diff --git a/crates/wasi-c2/src/snapshots/preview_1.rs b/crates/wasi-c2/src/snapshots/preview_1.rs index a6b1bdd816..fd50c5061b 100644 --- a/crates/wasi-c2/src/snapshots/preview_1.rs +++ b/crates/wasi-c2/src/snapshots/preview_1.rs @@ -178,7 +178,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } else if table.is::(fd) { // We cannot close preopened directories let dir_entry: RefMut = table.get(fd).unwrap(); - if dir_entry.preopen_path.is_some() { + if dir_entry.preopen_path().is_some() { return Err(Error::Notsup); } drop(dir_entry); @@ -230,17 +230,18 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { fs_rights_inheriting: types::Rights, ) -> Result<(), Error> { let table = self.table(); - let mut file_entry: RefMut = table.get(u32::from(fd))?; - let base_caps = FileCaps::try_from(&fs_rights_base)?; - let inheriting_caps = FileCaps::try_from(&fs_rights_inheriting)?; - if file_entry.base_caps.contains(&base_caps) - && file_entry.inheriting_caps.contains(&inheriting_caps) - { - file_entry.base_caps = base_caps; - file_entry.inheriting_caps = inheriting_caps; - Ok(()) + let fd = u32::from(fd); + if table.is::(fd) { + let mut file_entry: RefMut = table.get(fd)?; + let file_caps = FileCaps::from(&fs_rights_base); + file_entry.drop_caps_to(file_caps) + } else if table.is::(fd) { + let mut dir_entry: RefMut = table.get(fd)?; + let dir_caps = DirCaps::from(&fs_rights_base); + let file_caps = FileCaps::from(&fs_rights_inheriting); + dir_entry.drop_caps_to(dir_caps, file_caps) } else { - Err(Error::NotCapable) + Err(Error::Badf) } } @@ -412,7 +413,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { fn fd_prestat_get(&self, fd: types::Fd) -> Result { let table = self.table(); let dir_entry: RefMut = table.get(u32::from(fd)).map_err(|_| Error::Badf)?; - if let Some(ref preopen) = dir_entry.preopen_path { + if let Some(ref preopen) = dir_entry.preopen_path() { let path_str = preopen.to_str().ok_or(Error::Notsup)?; let pr_name_len = u32::try_from(path_str.as_bytes().len()).map_err(|_| Error::Overflow)?; @@ -430,7 +431,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { ) -> Result<(), Error> { let table = self.table(); let dir_entry: RefMut = table.get(u32::from(fd)).map_err(|_| Error::Notdir)?; - if let Some(ref preopen) = dir_entry.preopen_path { + if let Some(ref preopen) = dir_entry.preopen_path() { let path_bytes = preopen.to_str().ok_or(Error::Notsup)?.as_bytes(); let path_len = path_bytes.len(); if path_len < path_max_len as usize { @@ -573,7 +574,6 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { ) -> Result { let mut table = self.table(); let dir_entry: RefMut = table.get(u32::from(dirfd))?; - let dir = dir_entry.get_cap(DirCaps::OPEN)?; let symlink_follow = dirflags.contains(&types::Lookupflags::SYMLINK_FOLLOW); @@ -587,33 +587,28 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { { return Err(Error::Inval); } + let dir = dir_entry.get_cap(DirCaps::OPEN)?; let child_dir = dir.open_dir(symlink_follow, path.deref())?; - - let base_caps = DirCaps::from(&fs_rights_base); - let inheriting_caps = DirCaps::from(&fs_rights_inheriting); + let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_base)); + let dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_inheriting)); drop(dir); drop(dir_entry); - let fd = table.push(Box::new(DirEntry { - dir: child_dir, - base_caps, - inheriting_caps, - preopen_path: None, - }))?; + let fd = table.push(Box::new(DirEntry::new( + dir_caps, file_caps, None, child_dir, + )))?; Ok(types::Fd::from(fd)) } else { - // XXX go back and check these caps conversions - probably need to validate them - // against ??? - let base_caps = FileCaps::from(&fs_rights_base); - let inheriting_caps = FileCaps::from(&fs_rights_inheriting); + let mut required_caps = DirCaps::OPEN; + if oflags.contains(&OFlags::CREATE) { + required_caps = required_caps | DirCaps::CREATE_FILE; + } - let file = dir.open_file(symlink_follow, path.deref(), oflags, base_caps)?; + let dir = dir_entry.get_cap(required_caps)?; + let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_base)); + let file = dir.open_file(symlink_follow, path.deref(), oflags, file_caps)?; drop(dir); drop(dir_entry); - let fd = table.push(Box::new(FileEntry { - file, - base_caps, - inheriting_caps, - }))?; + let fd = table.push(Box::new(FileEntry::new(file_caps, file)))?; Ok(types::Fd::from(fd)) } } @@ -724,8 +719,8 @@ impl From<&FdStat> for types::Fdstat { fn from(fdstat: &FdStat) -> types::Fdstat { types::Fdstat { fs_filetype: types::Filetype::from(&fdstat.filetype), - fs_rights_base: types::Rights::from(&fdstat.base_caps), - fs_rights_inheriting: types::Rights::from(&fdstat.inheriting_caps), + fs_rights_base: types::Rights::from(&fdstat.caps), + fs_rights_inheriting: types::Rights::empty(), fs_flags: types::Fdflags::from(&fdstat.flags), } } @@ -735,8 +730,8 @@ impl From<&DirStat> for types::Fdstat { fn from(dirstat: &DirStat) -> types::Fdstat { types::Fdstat { fs_filetype: types::Filetype::Directory, - fs_rights_base: types::Rights::from(&dirstat.base_caps), - fs_rights_inheriting: types::Rights::from(&dirstat.inheriting_caps), + fs_rights_base: types::Rights::from(&dirstat.file_caps), + fs_rights_inheriting: types::Rights::from(&dirstat.dir_caps), fs_flags: types::Fdflags::empty(), } }