diff options
| author | David Sherret <dsherret@users.noreply.github.com> | 2024-09-16 21:39:37 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-16 21:39:37 +0100 |
| commit | 62e952559f600e72d7498c9b12f906cb0b1ba150 (patch) | |
| tree | 6dbcce6592973358ef4bf6341888b0bbbdb98cc5 /ext | |
| parent | e0b9c745c15720914f14996bf357d5b375e2dbd8 (diff) | |
refactor(permissions): split up Descriptor into Allow, Deny, and Query (#25508)
This makes the permission system more versatile.
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/fetch/lib.rs | 27 | ||||
| -rw-r--r-- | ext/ffi/call.rs | 4 | ||||
| -rw-r--r-- | ext/ffi/callback.rs | 2 | ||||
| -rw-r--r-- | ext/ffi/dlfcn.rs | 18 | ||||
| -rw-r--r-- | ext/ffi/lib.rs | 23 | ||||
| -rw-r--r-- | ext/ffi/repr.rs | 42 | ||||
| -rw-r--r-- | ext/fs/lib.rs | 80 | ||||
| -rw-r--r-- | ext/fs/ops.rs | 302 | ||||
| -rw-r--r-- | ext/kv/sqlite.rs | 74 | ||||
| -rw-r--r-- | ext/napi/lib.rs | 16 | ||||
| -rw-r--r-- | ext/net/lib.rs | 45 | ||||
| -rw-r--r-- | ext/net/ops.rs | 21 | ||||
| -rw-r--r-- | ext/net/ops_tls.rs | 9 | ||||
| -rw-r--r-- | ext/net/ops_unix.rs | 42 | ||||
| -rw-r--r-- | ext/node/lib.rs | 35 | ||||
| -rw-r--r-- | ext/node/ops/fs.rs | 71 |
16 files changed, 467 insertions, 344 deletions
diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 79659771e..88f303852 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -299,10 +299,15 @@ impl Drop for ResourceToBodyAdapter { pub trait FetchPermissions { fn check_net_url( &mut self, - _url: &Url, + url: &Url, api_name: &str, ) -> Result<(), AnyError>; - fn check_read(&mut self, _p: &Path, api_name: &str) -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_read<'a>( + &mut self, + p: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError>; } impl FetchPermissions for deno_permissions::PermissionsContainer { @@ -316,12 +321,16 @@ impl FetchPermissions for deno_permissions::PermissionsContainer { } #[inline(always)] - fn check_read( + fn check_read<'a>( &mut self, - path: &Path, + path: &'a Path, api_name: &str, - ) -> Result<(), AnyError> { - deno_permissions::PermissionsContainer::check_read(self, path, api_name) + ) -> Result<Cow<'a, Path>, AnyError> { + deno_permissions::PermissionsContainer::check_read_path( + self, + path, + Some(api_name), + ) } } @@ -359,7 +368,11 @@ where type_error("NetworkError when attempting to fetch resource") })?; let permissions = state.borrow_mut::<FP>(); - permissions.check_read(&path, "fetch()")?; + let path = permissions.check_read(&path, "fetch()")?; + let url = match path { + Cow::Owned(path) => Url::from_file_path(path).unwrap(), + Cow::Borrowed(_) => url, + }; if method != Method::GET { return Err(type_error(format!( diff --git a/ext/ffi/call.rs b/ext/ffi/call.rs index 380fc03a1..3572b9e81 100644 --- a/ext/ffi/call.rs +++ b/ext/ffi/call.rs @@ -287,7 +287,7 @@ where { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; }; let symbol = PtrSymbol::new(pointer, &def)?; @@ -384,7 +384,7 @@ where { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; }; let symbol = PtrSymbol::new(pointer, &def)?; diff --git a/ext/ffi/callback.rs b/ext/ffi/callback.rs index 7d0114131..6fa166f52 100644 --- a/ext/ffi/callback.rs +++ b/ext/ffi/callback.rs @@ -557,7 +557,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; let thread_id: u32 = LOCAL_THREAD_ID.with(|s| { let value = *s.borrow(); diff --git a/ext/ffi/dlfcn.rs b/ext/ffi/dlfcn.rs index 2bae5d223..10199bf85 100644 --- a/ext/ffi/dlfcn.rs +++ b/ext/ffi/dlfcn.rs @@ -19,7 +19,6 @@ use serde_value::ValueDeserializer; use std::borrow::Cow; use std::collections::HashMap; use std::ffi::c_void; -use std::path::PathBuf; use std::rc::Rc; pub struct DynamicLibraryResource { @@ -121,15 +120,13 @@ pub fn op_ffi_load<'scope, FP>( where FP: FfiPermissions + 'static, { - let path = args.path; - let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(Some(&PathBuf::from(&path)))?; + let path = permissions.check_partial_with_path(&args.path)?; let lib = Library::open(&path).map_err(|e| { dlopen2::Error::OpeningLibraryError(std::io::Error::new( std::io::ErrorKind::Other, - format_error(e, path), + format_error(e, &path), )) })?; let mut resource = DynamicLibraryResource { @@ -290,7 +287,10 @@ fn sync_fn_impl<'s>( // `path` is only used on Windows. #[allow(unused_variables)] -pub(crate) fn format_error(e: dlopen2::Error, path: String) -> String { +pub(crate) fn format_error( + e: dlopen2::Error, + path: &std::path::Path, +) -> String { match e { #[cfg(target_os = "windows")] // This calls FormatMessageW with library path @@ -300,7 +300,6 @@ pub(crate) fn format_error(e: dlopen2::Error, path: String) -> String { // // https://github.com/denoland/deno/issues/11632 dlopen2::Error::OpeningLibraryError(e) => { - use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use winapi::shared::minwindef::DWORD; use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER; @@ -324,7 +323,8 @@ pub(crate) fn format_error(e: dlopen2::Error, path: String) -> String { let mut buf = vec![0; 500]; - let path = OsStr::new(&path) + let path = path + .as_os_str() .encode_wide() .chain(Some(0)) .collect::<Vec<_>>(); @@ -384,7 +384,7 @@ mod tests { std::io::Error::from_raw_os_error(0x000000C1), ); assert_eq!( - format_error(err, "foo.dll".to_string()), + format_error(err, &std::path::PathBuf::from("foo.dll")), "foo.dll is not a valid Win32 application.\r\n".to_string(), ); } diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index 59d241c5a..77ec3c85e 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -5,7 +5,7 @@ use deno_core::error::AnyError; use std::mem::size_of; use std::os::raw::c_char; use std::os::raw::c_short; -use std::path::Path; +use std::path::PathBuf; mod call; mod callback; @@ -41,13 +41,28 @@ const _: () = { pub const UNSTABLE_FEATURE_NAME: &str = "ffi"; pub trait FfiPermissions { - fn check_partial(&mut self, path: Option<&Path>) -> Result<(), AnyError>; + fn check_partial_no_path(&mut self) -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_partial_with_path( + &mut self, + path: &str, + ) -> Result<PathBuf, AnyError>; } impl FfiPermissions for deno_permissions::PermissionsContainer { #[inline(always)] - fn check_partial(&mut self, path: Option<&Path>) -> Result<(), AnyError> { - deno_permissions::PermissionsContainer::check_ffi_partial(self, path) + fn check_partial_no_path(&mut self) -> Result<(), AnyError> { + deno_permissions::PermissionsContainer::check_ffi_partial_no_path(self) + } + + #[inline(always)] + fn check_partial_with_path( + &mut self, + path: &str, + ) -> Result<PathBuf, AnyError> { + deno_permissions::PermissionsContainer::check_ffi_partial_with_path( + self, path, + ) } } diff --git a/ext/ffi/repr.rs b/ext/ffi/repr.rs index f56537475..315e6d53b 100644 --- a/ext/ffi/repr.rs +++ b/ext/ffi/repr.rs @@ -21,7 +21,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; Ok(ptr_number as *mut c_void) } @@ -36,7 +36,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; Ok(a == b) } @@ -50,7 +50,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; Ok(buf as *mut c_void) } @@ -64,7 +64,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; let Some(buf) = buf.get_backing_store() else { return Ok(0 as _); @@ -85,7 +85,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid pointer to offset, pointer is null")); @@ -115,7 +115,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; Ok(ptr as usize) } @@ -132,7 +132,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid ArrayBuffer pointer, pointer is null")); @@ -164,7 +164,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if src.is_null() { Err(type_error("Invalid ArrayBuffer pointer, pointer is null")) @@ -195,7 +195,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid CString pointer, pointer is null")); @@ -221,7 +221,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid bool pointer, pointer is null")); @@ -241,7 +241,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid u8 pointer, pointer is null")); @@ -263,7 +263,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid i8 pointer, pointer is null")); @@ -285,7 +285,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid u16 pointer, pointer is null")); @@ -307,7 +307,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid i16 pointer, pointer is null")); @@ -329,7 +329,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid u32 pointer, pointer is null")); @@ -349,7 +349,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid i32 pointer, pointer is null")); @@ -372,7 +372,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid u64 pointer, pointer is null")); @@ -398,7 +398,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid i64 pointer, pointer is null")); @@ -421,7 +421,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid f32 pointer, pointer is null")); @@ -441,7 +441,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid f64 pointer, pointer is null")); @@ -461,7 +461,7 @@ where FP: FfiPermissions + 'static, { let permissions = state.borrow_mut::<FP>(); - permissions.check_partial(None)?; + permissions.check_partial_no_path()?; if ptr.is_null() { return Err(type_error("Invalid pointer pointer, pointer is null")); diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs index a6f273323..bd49078b2 100644 --- a/ext/fs/lib.rs +++ b/ext/fs/lib.rs @@ -24,6 +24,7 @@ use deno_core::error::AnyError; use deno_io::fs::FsError; use std::borrow::Cow; use std::path::Path; +use std::path::PathBuf; pub trait FsPermissions { fn check_open<'a>( @@ -34,8 +35,18 @@ pub trait FsPermissions { path: &'a Path, api_name: &str, ) -> Result<std::borrow::Cow<'a, Path>, FsError>; - fn check_read(&mut self, path: &Path, api_name: &str) - -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_read( + &mut self, + path: &str, + api_name: &str, + ) -> Result<PathBuf, AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_read_path<'a>( + &mut self, + path: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError>; fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>; fn check_read_blind( &mut self, @@ -43,16 +54,24 @@ pub trait FsPermissions { display: &str, api_name: &str, ) -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_write( &mut self, - path: &Path, + path: &str, api_name: &str, - ) -> Result<(), AnyError>; + ) -> Result<PathBuf, AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_write_path<'a>( + &mut self, + path: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_write_partial( &mut self, - path: &Path, + path: &str, api_name: &str, - ) -> Result<(), AnyError>; + ) -> Result<PathBuf, AnyError>; fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>; fn check_write_blind( &mut self, @@ -96,25 +115,44 @@ impl FsPermissions for deno_permissions::PermissionsContainer { // If somehow read or write aren't specified, use read let read = read || !write; + let mut path: Cow<'a, Path> = Cow::Borrowed(path); if read { - FsPermissions::check_read(self, path, api_name) + let resolved_path = FsPermissions::check_read_path(self, &path, api_name) .map_err(|_| FsError::NotCapable("read"))?; + if let Cow::Owned(resolved_path) = resolved_path { + path = Cow::Owned(resolved_path); + } } if write { - FsPermissions::check_write(self, path, api_name) - .map_err(|_| FsError::NotCapable("write"))?; + let resolved_path = + FsPermissions::check_write_path(self, &path, api_name) + .map_err(|_| FsError::NotCapable("write"))?; + if let Cow::Owned(resolved_path) = resolved_path { + path = Cow::Owned(resolved_path); + } } - Ok(Cow::Borrowed(path)) + Ok(path) } fn check_read( &mut self, - path: &Path, + path: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_read(self, path, api_name) } + fn check_read_path<'a>( + &mut self, + path: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError> { + deno_permissions::PermissionsContainer::check_read_path( + self, + path, + Some(api_name), + ) + } fn check_read_blind( &mut self, path: &Path, @@ -128,17 +166,27 @@ impl FsPermissions for deno_permissions::PermissionsContainer { fn check_write( &mut self, - path: &Path, + path: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_write(self, path, api_name) } + fn check_write_path<'a>( + &mut self, + path: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError> { + deno_permissions::PermissionsContainer::check_write_path( + self, path, api_name, + ) + } + fn check_write_partial( &mut self, - path: &Path, + path: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_write_partial( self, path, api_name, ) diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs index f25cd944d..150d3b955 100644 --- a/ext/fs/ops.rs +++ b/ext/fs/ops.rs @@ -5,6 +5,7 @@ use std::io; use std::io::SeekFrom; use std::path::Path; use std::path::PathBuf; +use std::path::StripPrefixError; use std::rc::Rc; use deno_core::anyhow::bail; @@ -105,8 +106,9 @@ pub fn op_fs_chdir<P>( where P: FsPermissions + 'static, { - let d = PathBuf::from(&directory); - state.borrow_mut::<P>().check_read(&d, "Deno.chdir()")?; + let d = state + .borrow_mut::<P>() + .check_read(directory, "Deno.chdir()")?; state .borrow::<FileSystemRc>() .chdir(&d) @@ -188,11 +190,9 @@ pub fn op_fs_mkdir_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - let mode = mode.unwrap_or(0o777) & 0o777; - state + let path = state .borrow_mut::<P>() .check_write(&path, "Deno.mkdirSync()")?; @@ -213,14 +213,12 @@ pub async fn op_fs_mkdir_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - let mode = mode.unwrap_or(0o777) & 0o777; - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write(&path, "Deno.mkdir()")?; - state.borrow::<FileSystemRc>().clone() + let path = state.borrow_mut::<P>().check_write(&path, "Deno.mkdir()")?; + (state.borrow::<FileSystemRc>().clone(), path) }; fs.mkdir_async(path.clone(), recursive, mode) @@ -239,8 +237,7 @@ pub fn op_fs_chmod_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - state + let path = state .borrow_mut::<P>() .check_write(&path, "Deno.chmodSync()")?; let fs = state.borrow::<FileSystemRc>(); @@ -257,11 +254,10 @@ pub async fn op_fs_chmod_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write(&path, "Deno.chmod()")?; - state.borrow::<FileSystemRc>().clone() + let path = state.borrow_mut::<P>().check_write(&path, "Deno.chmod()")?; + (state.borrow::<FileSystemRc>().clone(), path) }; fs.chmod_async(path.clone(), mode) .await @@ -279,8 +275,7 @@ pub fn op_fs_chown_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - state + let path = state .borrow_mut::<P>() .check_write(&path, "Deno.chownSync()")?; let fs = state.borrow::<FileSystemRc>(); @@ -299,11 +294,10 @@ pub async fn op_fs_chown_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write(&path, "Deno.chown()")?; - state.borrow::<FileSystemRc>().clone() + let path = state.borrow_mut::<P>().check_write(&path, "Deno.chown()")?; + (state.borrow::<FileSystemRc>().clone(), path) }; fs.chown_async(path.clone(), uid, gid) .await @@ -320,11 +314,9 @@ pub fn op_fs_remove_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - state + let path = state .borrow_mut::<P>() - .check_write(&path, "Deno.removeSync()")?; + .check_write(path, "Deno.removeSync()")?; let fs = state.borrow::<FileSystemRc>(); fs.remove_sync(&path, recursive) @@ -342,21 +334,19 @@ pub async fn op_fs_remove_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - if recursive { + let path = if recursive { state .borrow_mut::<P>() - .check_write(&path, "Deno.remove()")?; + .check_write(&path, "Deno.remove()")? } else { state .borrow_mut::<P>() - .check_write_partial(&path, "Deno.remove()")?; - } + .check_write_partial(&path, "Deno.remove()")? + }; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path) }; fs.remove_async(path.clone(), recursive) @@ -375,12 +365,9 @@ pub fn op_fs_copy_file_sync<P>( where P: FsPermissions + 'static, { - let from = PathBuf::from(from); - let to = PathBuf::from(to); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&from, "Deno.copyFileSync()")?; - permissions.check_write(&to, "Deno.copyFileSync()")?; + let from = permissions.check_read(from, "Deno.copyFileSync()")?; + let to = permissions.check_write(to, "Deno.copyFileSync()")?; let fs = state.borrow::<FileSystemRc>(); fs.copy_file_sync(&from, &to) @@ -398,15 +385,12 @@ pub async fn op_fs_copy_file_async<P>( where P: FsPermissions + 'static, { - let from = PathBuf::from(from); - let to = PathBuf::from(to); - - let fs = { + let (fs, from, to) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::<P>(); - permissions.check_read(&from, "Deno.copyFile()")?; - permissions.check_write(&to, "Deno.copyFile()")?; - state.borrow::<FileSystemRc>().clone() + let from = permissions.check_read(&from, "Deno.copyFile()")?; + let to = permissions.check_write(&to, "Deno.copyFile()")?; + (state.borrow::<FileSystemRc>().clone(), from, to) }; fs.copy_file_async(from.clone(), to.clone()) @@ -425,8 +409,7 @@ pub fn op_fs_stat_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - state + let path = state .borrow_mut::<P>() .check_read(&path, "Deno.statSync()")?; let fs = state.borrow::<FileSystemRc>(); @@ -445,12 +428,11 @@ pub async fn op_fs_stat_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.stat()")?; - state.borrow::<FileSystemRc>().clone() + let path = permissions.check_read(&path, "Deno.stat()")?; + (state.borrow::<FileSystemRc>().clone(), path) }; let stat = fs .stat_async(path.clone()) @@ -468,8 +450,7 @@ pub fn op_fs_lstat_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - state + let path = state .borrow_mut::<P>() .check_read(&path, "Deno.lstatSync()")?; let fs = state.borrow::<FileSystemRc>(); @@ -488,12 +469,11 @@ pub async fn op_fs_lstat_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.lstat()")?; - state.borrow::<FileSystemRc>().clone() + let path = permissions.check_read(&path, "Deno.lstat()")?; + (state.borrow::<FileSystemRc>().clone(), path) }; let stat = fs .lstat_async(path.clone()) @@ -511,11 +491,9 @@ pub fn op_fs_realpath_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - let fs = state.borrow::<FileSystemRc>().clone(); let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.realPathSync()")?; + let path = permissions.check_read(&path, "Deno.realPathSync()")?; if path.is_relative() { permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPathSync()")?; } @@ -536,18 +514,16 @@ pub async fn op_fs_realpath_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - let fs; - { + let (fs, path) = { let mut state = state.borrow_mut(); - fs = state.borrow::<FileSystemRc>().clone(); + let fs = state.borrow::<FileSystemRc>().clone(); let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.realPath()")?; + let path = permissions.check_read(&path, "Deno.realPath()")?; if path.is_relative() { permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPath()")?; } - } + (fs, path) + }; let resolved_path = fs .realpath_async(path.clone()) .await @@ -566,9 +542,7 @@ pub fn op_fs_read_dir_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - state + let path = state .borrow_mut::<P>() .check_read(&path, "Deno.readDirSync()")?; @@ -587,14 +561,12 @@ pub async fn op_fs_read_dir_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() .check_read(&path, "Deno.readDir()")?; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path) }; let entries = fs @@ -614,13 +586,10 @@ pub fn op_fs_rename_sync<P>( where P: FsPermissions + 'static, { - let oldpath = PathBuf::from(oldpath); - let newpath = PathBuf::from(newpath); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.renameSync()")?; - permissions.check_write(&oldpath, "Deno.renameSync()")?; - permissions.check_write(&newpath, "Deno.renameSync()")?; + let _ = permissions.check_read(&oldpath, "Deno.renameSync()")?; + let oldpath = permissions.check_write(&oldpath, "Deno.renameSync()")?; + let newpath = permissions.check_write(&newpath, "Deno.renameSync()")?; let fs = state.borrow::<FileSystemRc>(); fs.rename_sync(&oldpath, &newpath) @@ -638,16 +607,13 @@ pub async fn op_fs_rename_async<P>( where P: FsPermissions + 'static, { - let oldpath = PathBuf::from(oldpath); - let newpath = PathBuf::from(newpath); - - let fs = { + let (fs, oldpath, newpath) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.rename()")?; - permissions.check_write(&oldpath, "Deno.rename()")?; - permissions.check_write(&newpath, "Deno.rename()")?; - state.borrow::<FileSystemRc>().clone() + _ = permissions.check_read(&oldpath, "Deno.rename()")?; + let oldpath = permissions.check_write(&oldpath, "Deno.rename()")?; + let newpath = permissions.check_write(&newpath, "Deno.rename()")?; + (state.borrow::<FileSystemRc>().clone(), oldpath, newpath) }; fs.rename_async(oldpath.clone(), newpath.clone()) @@ -666,14 +632,11 @@ pub fn op_fs_link_sync<P>( where P: FsPermissions + 'static, { - let oldpath = PathBuf::from(oldpath); - let newpath = PathBuf::from(newpath); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.linkSync()")?; - permissions.check_write(&oldpath, "Deno.linkSync()")?; - permissions.check_read(&newpath, "Deno.linkSync()")?; - permissions.check_write(&newpath, "Deno.linkSync()")?; + _ = permissions.check_read(oldpath, "Deno.linkSync()")?; + let oldpath = permissions.check_write(oldpath, "Deno.linkSync()")?; + _ = permissions.check_read(newpath, "Deno.linkSync()")?; + let newpath = permissions.check_write(newpath, "Deno.linkSync()")?; let fs = state.borrow::<FileSystemRc>(); fs.link_sync(&oldpath, &newpath) @@ -691,17 +654,14 @@ pub async fn op_fs_link_async<P>( where P: FsPermissions + 'static, { - let oldpath = PathBuf::from(&oldpath); - let newpath = PathBuf::from(&newpath); - - let fs = { + let (fs, oldpath, newpath) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.link()")?; - permissions.check_write(&oldpath, "Deno.link()")?; - permissions.check_read(&newpath, "Deno.link()")?; - permissions.check_write(&newpath, "Deno.link()")?; - state.borrow::<FileSystemRc>().clone() + _ = permissions.check_read(&oldpath, "Deno.link()")?; + let oldpath = permissions.check_write(&oldpath, "Deno.link()")?; + _ = permissions.check_read(&newpath, "Deno.link()")?; + let newpath = permissions.check_write(&newpath, "Deno.link()")?; + (state.borrow::<FileSystemRc>().clone(), oldpath, newpath) }; fs.link_async(oldpath.clone(), newpath.clone()) @@ -772,9 +732,7 @@ pub fn op_fs_read_link_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - state + let path = state .borrow_mut::<P>() .check_read(&path, "Deno.readLink()")?; @@ -794,14 +752,12 @@ pub async fn op_fs_read_link_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() .check_read(&path, "Deno.readLink()")?; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path) }; let target = fs @@ -821,11 +777,9 @@ pub fn op_fs_truncate_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - state + let path = state .borrow_mut::<P>() - .check_write(&path, "Deno.truncateSync()")?; + .check_write(path, "Deno.truncateSync()")?; let fs = state.borrow::<FileSystemRc>(); fs.truncate_sync(&path, len) @@ -843,14 +797,12 @@ pub async fn op_fs_truncate_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() .check_write(&path, "Deno.truncate()")?; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path) }; fs.truncate_async(path.clone(), len) @@ -872,9 +824,7 @@ pub fn op_fs_utime_sync<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - state.borrow_mut::<P>().check_write(&path, "Deno.utime()")?; + let path = state.borrow_mut::<P>().check_write(path, "Deno.utime()")?; let fs = state.borrow::<FileSystemRc>(); fs.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos) @@ -895,12 +845,10 @@ pub async fn op_fs_utime_async<P>( where P: FsPermissions + 'static, { - let path = PathBuf::from(path); - - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write(&path, "Deno.utime()")?; - state.borrow::<FileSystemRc>().clone() + let path = state.borrow_mut::<P>().check_write(&path, "Deno.utime()")?; + (state.borrow::<FileSystemRc>().clone(), path) }; fs.utime_async( @@ -920,15 +868,18 @@ where #[string] pub fn op_fs_make_temp_dir_sync<P>( state: &mut OpState, - #[string] dir: Option<String>, + #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, ) -> Result<String, AnyError> where P: FsPermissions + 'static, { - let (dir, fs) = - make_temp_check_sync::<P>(state, dir, "Deno.makeTempDirSync()")?; + let (dir, fs) = make_temp_check_sync::<P>( + state, + dir_arg.as_deref(), + "Deno.makeTempDirSync()", + )?; let mut rng = thread_rng(); @@ -936,7 +887,11 @@ where for _ in 0..MAX_TRIES { let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?; match fs.mkdir_sync(&path, false, 0o700) { - Ok(_) => return path_into_string(path.into_os_string()), + Ok(_) => { + // PERMISSIONS: ensure the absolute path is not leaked + let path = strip_dir_prefix(&dir, dir_arg.as_deref(), path)?; + return path_into_string(path.into_os_string()); + } Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => { continue; } @@ -955,14 +910,18 @@ where #[string] pub async fn op_fs_make_temp_dir_async<P>( state: Rc<RefCell<OpState>>, - #[string] dir: Option<String>, + #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, ) -> Result<String, AnyError> where P: FsPermissions + 'static, { - let (dir, fs) = make_temp_check_async::<P>(state, dir, "Deno.makeTempDir()")?; + let (dir, fs) = make_temp_check_async::<P>( + state, + dir_arg.as_deref(), + "Deno.makeTempDir()", + )?; let mut rng = thread_rng(); @@ -970,7 +929,11 @@ where for _ in 0..MAX_TRIES { let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?; match fs.clone().mkdir_async(path.clone(), false, 0o700).await { - Ok(_) => return path_into_string(path.into_os_string()), + Ok(_) => { + // PERMISSIONS: ensure the absolute path is not leaked + let path = strip_dir_prefix(&dir, dir_arg.as_deref(), path)?; + return path_into_string(path.into_os_string()); + } Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => { continue; } @@ -989,15 +952,18 @@ where #[string] pub fn op_fs_make_temp_file_sync<P>( state: &mut OpState, - #[string] dir: Option<String>, + #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, ) -> Result<String, AnyError> where P: FsPermissions + 'static, { - let (dir, fs) = - make_temp_check_sync::<P>(state, dir, "Deno.makeTempFileSync()")?; + let (dir, fs) = make_temp_check_sync::<P>( + state, + dir_arg.as_deref(), + "Deno.makeTempFileSync()", + )?; let open_opts = OpenOptions { write: true, @@ -1011,7 +977,11 @@ where for _ in 0..MAX_TRIES { let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?; match fs.open_sync(&path, open_opts, None) { - Ok(_) => return path_into_string(path.into_os_string()), + Ok(_) => { + // PERMISSIONS: ensure the absolute path is not leaked + let path = strip_dir_prefix(&dir, dir_arg.as_deref(), path)?; + return path_into_string(path.into_os_string()); + } Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => { continue; } @@ -1030,15 +1000,18 @@ where #[string] pub async fn op_fs_make_temp_file_async<P>( state: Rc<RefCell<OpState>>, - #[string] dir: Option<String>, + #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, ) -> Result<String, AnyError> where P: FsPermissions + 'static, { - let (dir, fs) = - make_temp_check_async::<P>(state, dir, "Deno.makeTempFile()")?; + let (dir, fs) = make_temp_check_async::<P>( + state, + dir_arg.as_deref(), + "Deno.makeTempFile()", + )?; let open_opts = OpenOptions { write: true, @@ -1053,7 +1026,11 @@ where for _ in 0..MAX_TRIES { let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?; match fs.clone().open_async(path.clone(), open_opts, None).await { - Ok(_) => return path_into_string(path.into_os_string()), + Ok(_) => { + // PERMISSIONS: ensure the absolute path is not leaked + let path = strip_dir_prefix(&dir, dir_arg.as_deref(), path)?; + return path_into_string(path.into_os_string()); + } Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => { continue; } @@ -1067,9 +1044,26 @@ where .context("tmpfile") } +fn strip_dir_prefix( + resolved_dir: &Path, + dir_arg: Option<&str>, + result_path: PathBuf, +) -> Result<PathBuf, StripPrefixError> { + if resolved_dir.is_absolute() { + match &dir_arg { + Some(dir_arg) => { + Ok(Path::new(dir_arg).join(result_path.strip_prefix(resolved_dir)?)) + } + None => Ok(result_path), + } + } else { + Ok(result_path) + } +} + fn make_temp_check_sync<P>( state: &mut OpState, - dir: Option<String>, + dir: Option<&str>, api_name: &str, ) -> Result<(PathBuf, FileSystemRc), AnyError> where @@ -1077,11 +1071,7 @@ where { let fs = state.borrow::<FileSystemRc>().clone(); let dir = match dir { - Some(dir) => { - let dir = PathBuf::from(dir); - state.borrow_mut::<P>().check_write(&dir, api_name)?; - dir - } + Some(dir) => state.borrow_mut::<P>().check_write(dir, api_name)?, None => { let dir = fs.tmp_dir().context("tmpdir")?; state @@ -1095,7 +1085,7 @@ where fn make_temp_check_async<P>( state: Rc<RefCell<OpState>>, - dir: Option<String>, + dir: Option<&str>, api_name: &str, ) -> Result<(PathBuf, FileSystemRc), AnyError> where @@ -1104,11 +1094,7 @@ where let mut state = state.borrow_mut(); let fs = state.borrow::<FileSystemRc>().clone(); let dir = match dir { - Some(dir) => { - let dir = PathBuf::from(dir); - state.borrow_mut::<P>().check_write(&dir, api_name)?; - dir - } + Some(dir) => state.borrow_mut::<P>().check_write(dir, api_name)?, None => { let dir = fs.tmp_dir().context("tmpdir")?; state diff --git a/ext/kv/sqlite.rs b/ext/kv/sqlite.rs index b31fd1736..8027ff03d 100644 --- a/ext/kv/sqlite.rs +++ b/ext/kv/sqlite.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::env::current_dir; @@ -36,19 +37,37 @@ pub struct SqliteDbHandler<P: SqliteDbHandlerPermissions + 'static> { } pub trait SqliteDbHandlerPermissions { - fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>; - fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_read( + &mut self, + p: &str, + api_name: &str, + ) -> Result<PathBuf, AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_write<'a>( + &mut self, + p: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError>; } impl SqliteDbHandlerPermissions for deno_permissions::PermissionsContainer { #[inline(always)] - fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError> { + fn check_read( + &mut self, + p: &str, + api_name: &str, + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_read(self, p, api_name) } #[inline(always)] - fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError> { - deno_permissions::PermissionsContainer::check_write(self, p, api_name) + fn check_write<'a>( + &mut self, + p: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError> { + deno_permissions::PermissionsContainer::check_write_path(self, p, api_name) } } @@ -74,28 +93,35 @@ impl<P: SqliteDbHandlerPermissions> DatabaseHandler for SqliteDbHandler<P> { state: Rc<RefCell<OpState>>, path: Option<String>, ) -> Result<Self::DB, AnyError> { - // Validate path - if let Some(path) = &path { - if path != ":memory:" { - if path.is_empty() { - return Err(type_error("Filename cannot be empty")); - } - if path.starts_with(':') { - return Err(type_error( - "Filename cannot start with ':' unless prefixed with './'", - )); - } - let path = Path::new(path); - { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(path, "Deno.openKv")?; - permissions.check_write(path, "Deno.openKv")?; - } + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn validate_path<P: SqliteDbHandlerPermissions + 'static>( + state: &RefCell<OpState>, + path: Option<String>, + ) -> Result<Option<String>, AnyError> { + let Some(path) = path else { + return Ok(None); + }; + if path == ":memory:" { + return Ok(Some(path)); + } + if path.is_empty() { + return Err(type_error("Filename cannot be empty")); + } + if path.starts_with(':') { + return Err(type_error( + "Filename cannot start with ':' unless prefixed with './'", + )); + } + { + let mut state = state.borrow_mut(); + let permissions = state.borrow_mut::<P>(); + let path = permissions.check_read(&path, "Deno.openKv")?; + let path = permissions.check_write(&path, "Deno.openKv")?; + Ok(Some(path.to_string_lossy().to_string())) } } - let path = path.clone(); + let path = validate_path::<P>(&state, path)?; let default_storage_dir = self.default_storage_dir.clone(); type ConnGen = Arc<dyn Fn() -> rusqlite::Result<rusqlite::Connection> + Send + Sync>; diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs index c9dc62a8b..4500c66fd 100644 --- a/ext/napi/lib.rs +++ b/ext/napi/lib.rs @@ -16,7 +16,6 @@ use deno_core::OpState; use deno_core::V8CrossThreadTaskSpawner; use std::cell::RefCell; use std::collections::HashMap; -use std::path::Path; use std::path::PathBuf; use std::rc::Rc; use std::thread_local; @@ -482,15 +481,15 @@ deno_core::extension!(deno_napi, ); pub trait NapiPermissions { - fn check(&mut self, path: Option<&Path>) - -> std::result::Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check(&mut self, path: &str) -> std::result::Result<PathBuf, AnyError>; } // NOTE(bartlomieju): for now, NAPI uses `--allow-ffi` flag, but that might // change in the future. impl NapiPermissions for deno_permissions::PermissionsContainer { #[inline(always)] - fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { + fn check(&mut self, path: &str) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_ffi(self, path) } } @@ -501,7 +500,7 @@ unsafe impl Send for NapiModuleHandle {} struct NapiModuleHandle(*const NapiModule); static NAPI_LOADED_MODULES: std::sync::LazyLock< - RwLock<HashMap<String, NapiModuleHandle>>, + RwLock<HashMap<PathBuf, NapiModuleHandle>>, > = std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); #[op2(reentrant)] @@ -519,15 +518,16 @@ where { // We must limit the OpState borrow because this function can trigger a // re-borrow through the NAPI module. - let (async_work_sender, cleanup_hooks, external_ops_tracker) = { + let (async_work_sender, cleanup_hooks, external_ops_tracker, path) = { let mut op_state = op_state.borrow_mut(); let permissions = op_state.borrow_mut::<NP>(); - permissions.check(Some(&PathBuf::from(&path)))?; + let path = permissions.check(&path)?; let napi_state = op_state.borrow::<NapiState>(); ( op_state.borrow::<V8CrossThreadTaskSpawner>().clone(), napi_state.env_cleanup_hooks.clone(), op_state.external_ops_tracker.clone(), + path, ) }; @@ -612,7 +612,7 @@ where } else { return Err(type_error(format!( "Unable to find register Node-API module at {}", - path + path.display() ))); }; diff --git a/ext/net/lib.rs b/ext/net/lib.rs index 098d220db..0ef3e85c4 100644 --- a/ext/net/lib.rs +++ b/ext/net/lib.rs @@ -13,6 +13,7 @@ use deno_core::error::AnyError; use deno_core::OpState; use deno_tls::rustls::RootCertStore; use deno_tls::RootCertStoreProvider; +use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -22,12 +23,27 @@ pub const UNSTABLE_FEATURE_NAME: &str = "net"; pub trait NetPermissions { fn check_net<T: AsRef<str>>( &mut self, - _host: &(T, Option<u16>), - _api_name: &str, + host: &(T, Option<u16>), + api_name: &str, ) -> Result<(), AnyError>; - fn check_read(&mut self, _p: &Path, _api_name: &str) -> Result<(), AnyError>; - fn check_write(&mut self, _p: &Path, _api_name: &str) - -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_read( + &mut self, + p: &str, + api_name: &str, + ) -> Result<PathBuf, AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_write( + &mut self, + p: &str, + api_name: &str, + ) -> Result<PathBuf, AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_write_path<'a>( + &mut self, + p: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError>; } impl NetPermissions for deno_permissions::PermissionsContainer { @@ -43,20 +59,31 @@ impl NetPermissions for deno_permissions::PermissionsContainer { #[inline(always)] fn check_read( &mut self, - path: &Path, + path: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_read(self, path, api_name) } #[inline(always)] fn check_write( &mut self, - path: &Path, + path: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_write(self, path, api_name) } + + #[inline(always)] + fn check_write_path<'a>( + &mut self, + path: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError> { + deno_permissions::PermissionsContainer::check_write_path( + self, path, api_name, + ) + } } /// Helper for checking unstable features. Used for sync ops. diff --git a/ext/net/ops.rs b/ext/net/ops.rs index b74dc8d75..67d32fe03 100644 --- a/ext/net/ops.rs +++ b/ext/net/ops.rs @@ -784,6 +784,7 @@ mod tests { use std::net::Ipv6Addr; use std::net::ToSocketAddrs; use std::path::Path; + use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; use trust_dns_proto::rr::rdata::a::A; @@ -991,18 +992,26 @@ mod tests { fn check_read( &mut self, - _p: &Path, + p: &str, _api_name: &str, - ) -> Result<(), AnyError> { - Ok(()) + ) -> Result<PathBuf, AnyError> { + Ok(PathBuf::from(p)) } fn check_write( &mut self, - _p: &Path, + p: &str, _api_name: &str, - ) -> Result<(), AnyError> { - Ok(()) + ) -> Result<PathBuf, AnyError> { + Ok(PathBuf::from(p)) + } + + fn check_write_path<'a>( + &mut self, + p: &'a Path, + _api_name: &str, + ) -> Result<Cow<'a, Path>, AnyError> { + Ok(Cow::Borrowed(p)) } } diff --git a/ext/net/ops_tls.rs b/ext/net/ops_tls.rs index 3ca5adbbe..064da5818 100644 --- a/ext/net/ops_tls.rs +++ b/ext/net/ops_tls.rs @@ -52,7 +52,6 @@ use std::io::ErrorKind; use std::io::Read; use std::net::SocketAddr; use std::num::NonZeroUsize; -use std::path::Path; use std::rc::Rc; use std::sync::Arc; use tokio::io::AsyncReadExt; @@ -356,15 +355,17 @@ where .try_borrow::<UnsafelyIgnoreCertificateErrors>() .and_then(|it| it.0.clone()); - { + let cert_file = { let mut s = state.borrow_mut(); let permissions = s.borrow_mut::<NP>(); permissions .check_net(&(&addr.hostname, Some(addr.port)), "Deno.connectTls()")?; if let Some(path) = cert_file { - permissions.check_read(Path::new(path), "Deno.connectTls()")?; + Some(permissions.check_read(path, "Deno.connectTls()")?) + } else { + None } - } + }; let mut ca_certs = args .ca_certs diff --git a/ext/net/ops_unix.rs b/ext/net/ops_unix.rs index 7d2f6af3c..95293284f 100644 --- a/ext/net/ops_unix.rs +++ b/ext/net/ops_unix.rs @@ -94,22 +94,22 @@ pub async fn op_net_accept_unix( #[serde] pub async fn op_net_connect_unix<NP>( state: Rc<RefCell<OpState>>, - #[string] path: String, + #[string] address_path: String, ) -> Result<(ResourceId, Option<String>, Option<String>), AnyError> where NP: NetPermissions + 'static, { - let address_path = Path::new(&path); - { + let address_path = { let mut state_ = state.borrow_mut(); - state_ + let address_path = state_ .borrow_mut::<NP>() - .check_read(address_path, "Deno.connect()")?; - state_ + .check_read(&address_path, "Deno.connect()")?; + _ = state_ .borrow_mut::<NP>() - .check_write(address_path, "Deno.connect()")?; - } - let unix_stream = UnixStream::connect(Path::new(&path)).await?; + .check_write_path(&address_path, "Deno.connect()")?; + address_path + }; + let unix_stream = UnixStream::connect(&address_path).await?; let local_addr = unix_stream.local_addr()?; let remote_addr = unix_stream.peer_addr()?; let local_addr_path = local_addr.as_pathname().map(pathstring).transpose()?; @@ -148,18 +148,17 @@ pub async fn op_net_recv_unixpacket( pub async fn op_net_send_unixpacket<NP>( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, - #[string] path: String, + #[string] address_path: String, #[buffer] zero_copy: JsBuffer, ) -> Result<usize, AnyError> where NP: NetPermissions + 'static, { - let address_path = Path::new(&path); - { + let address_path = { let mut s = state.borrow_mut(); s.borrow_mut::<NP>() - .check_write(address_path, "Deno.DatagramConn.send()")?; - } + .check_write(&address_path, "Deno.DatagramConn.send()")? + }; let resource = state .borrow() @@ -178,17 +177,16 @@ where #[serde] pub fn op_net_listen_unix<NP>( state: &mut OpState, - #[string] path: String, + #[string] address_path: String, #[string] api_name: String, ) -> Result<(ResourceId, Option<String>), AnyError> where NP: NetPermissions + 'static, { - let address_path = Path::new(&path); let permissions = state.borrow_mut::<NP>(); let api_call_expr = format!("{}()", api_name); - permissions.check_read(address_path, &api_call_expr)?; - permissions.check_write(address_path, &api_call_expr)?; + let address_path = permissions.check_read(&address_path, &api_call_expr)?; + _ = permissions.check_write_path(&address_path, &api_call_expr)?; let listener = UnixListener::bind(address_path)?; let local_addr = listener.local_addr()?; let pathname = local_addr.as_pathname().map(pathstring).transpose()?; @@ -199,15 +197,15 @@ where pub fn net_listen_unixpacket<NP>( state: &mut OpState, - path: String, + address_path: String, ) -> Result<(ResourceId, Option<String>), AnyError> where NP: NetPermissions + 'static, { - let address_path = Path::new(&path); let permissions = state.borrow_mut::<NP>(); - permissions.check_read(address_path, "Deno.listenDatagram()")?; - permissions.check_write(address_path, "Deno.listenDatagram()")?; + let address_path = + permissions.check_read(&address_path, "Deno.listenDatagram()")?; + _ = permissions.check_write_path(&address_path, "Deno.listenDatagram()")?; let socket = UnixDatagram::bind(address_path)?; let local_addr = socket.local_addr()?; let pathname = local_addr.as_pathname().map(pathstring).transpose()?; diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 75581c128..fd9d4f9af 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -3,8 +3,10 @@ #![deny(clippy::print_stderr)] #![deny(clippy::print_stdout)] +use std::borrow::Cow; use std::collections::HashSet; use std::path::Path; +use std::path::PathBuf; use deno_core::error::AnyError; use deno_core::located_script_name; @@ -49,21 +51,29 @@ pub trait NodePermissions { url: &Url, api_name: &str, ) -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] #[inline(always)] - fn check_read(&mut self, path: &Path) -> Result<(), AnyError> { + fn check_read(&mut self, path: &str) -> Result<PathBuf, AnyError> { self.check_read_with_api_name(path, None) } + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_read_with_api_name( &mut self, - path: &Path, + path: &str, api_name: Option<&str>, - ) -> Result<(), AnyError>; + ) -> Result<PathBuf, AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn check_read_path<'a>( + &mut self, + path: &'a Path, + ) -> Result<Cow<'a, Path>, AnyError>; fn check_sys(&mut self, kind: &str, api_name: &str) -> Result<(), AnyError>; + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_write_with_api_name( &mut self, - path: &Path, + path: &str, api_name: Option<&str>, - ) -> Result<(), AnyError>; + ) -> Result<PathBuf, AnyError>; } impl NodePermissions for deno_permissions::PermissionsContainer { @@ -79,20 +89,27 @@ impl NodePermissions for deno_permissions::PermissionsContainer { #[inline(always)] fn check_read_with_api_name( &mut self, - path: &Path, + path: &str, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_read_with_api_name( self, path, api_name, ) } + fn check_read_path<'a>( + &mut self, + path: &'a Path, + ) -> Result<Cow<'a, Path>, AnyError> { + deno_permissions::PermissionsContainer::check_read_path(self, path, None) + } + #[inline(always)] fn check_write_with_api_name( &mut self, - path: &Path, + path: &str, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<PathBuf, AnyError> { deno_permissions::PermissionsContainer::check_write_with_api_name( self, path, api_name, ) diff --git a/ext/node/ops/fs.rs b/ext/node/ops/fs.rs index 687903325..6253f32d0 100644 --- a/ext/node/ops/fs.rs +++ b/ext/node/ops/fs.rs @@ -1,8 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::cell::RefCell; -use std::path::Path; -use std::path::PathBuf; use std::rc::Rc; use deno_core::error::AnyError; @@ -21,8 +19,7 @@ pub fn op_node_fs_exists_sync<P>( where P: NodePermissions + 'static, { - let path = PathBuf::from(path); - state + let path = state .borrow_mut::<P>() .check_read_with_api_name(&path, Some("node:fs.existsSync()"))?; let fs = state.borrow::<FileSystemRc>(); @@ -37,14 +34,12 @@ pub async fn op_node_fs_exists<P>( where P: NodePermissions + 'static, { - let path = PathBuf::from(path); - - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() .check_read_with_api_name(&path, Some("node:fs.exists()"))?; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path) }; Ok(fs.exists_async(path).await?) @@ -59,18 +54,15 @@ pub fn op_node_cp_sync<P>( where P: NodePermissions + 'static, { - let path = Path::new(path); - let new_path = Path::new(new_path); - - state + let path = state .borrow_mut::<P>() .check_read_with_api_name(path, Some("node:fs.cpSync"))?; - state + let new_path = state .borrow_mut::<P>() .check_write_with_api_name(new_path, Some("node:fs.cpSync"))?; let fs = state.borrow::<FileSystemRc>(); - fs.cp_sync(path, new_path)?; + fs.cp_sync(&path, &new_path)?; Ok(()) } @@ -83,18 +75,15 @@ pub async fn op_node_cp<P>( where P: NodePermissions + 'static, { - let path = PathBuf::from(path); - let new_path = PathBuf::from(new_path); - - let fs = { + let (fs, path, new_path) = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() .check_read_with_api_name(&path, Some("node:fs.cpSync"))?; - state + let new_path = state .borrow_mut::<P>() .check_write_with_api_name(&new_path, Some("node:fs.cpSync"))?; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path, new_path) }; fs.cp_async(path, new_path).await?; @@ -123,21 +112,21 @@ pub fn op_node_statfs<P>( where P: NodePermissions + 'static, { - { + let path = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() - .check_read_with_api_name(Path::new(&path), Some("node:fs.statfs"))?; + .check_read_with_api_name(&path, Some("node:fs.statfs"))?; state .borrow_mut::<P>() .check_sys("statfs", "node:fs.statfs")?; - } + path + }; #[cfg(unix)] { - use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; - let path = OsStr::new(&path); + let path = path.as_os_str(); let mut cpath = path.as_bytes().to_vec(); cpath.push(0); if bigint { @@ -196,7 +185,7 @@ where // Using a vfs here doesn't make sense, it won't align with the windows API // call below. #[allow(clippy::disallowed_methods)] - let path = Path::new(&path).canonicalize()?; + let path = path.canonicalize()?; let root = path .ancestors() .last() @@ -256,14 +245,12 @@ pub fn op_node_lutimes_sync<P>( where P: NodePermissions + 'static, { - let path = Path::new(path); - - state + let path = state .borrow_mut::<P>() .check_write_with_api_name(path, Some("node:fs.lutimes"))?; let fs = state.borrow::<FileSystemRc>(); - fs.lutime_sync(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)?; + fs.lutime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)?; Ok(()) } @@ -279,14 +266,12 @@ pub async fn op_node_lutimes<P>( where P: NodePermissions + 'static, { - let path = PathBuf::from(path); - - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() .check_write_with_api_name(&path, Some("node:fs.lutimesSync"))?; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path) }; fs.lutime_async(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos) @@ -305,8 +290,7 @@ pub fn op_node_lchown_sync<P>( where P: NodePermissions + 'static, { - let path = PathBuf::from(path); - state + let path = state .borrow_mut::<P>() .check_write_with_api_name(&path, Some("node:fs.lchownSync"))?; let fs = state.borrow::<FileSystemRc>(); @@ -324,13 +308,12 @@ pub async fn op_node_lchown<P>( where P: NodePermissions + 'static, { - let path = PathBuf::from(path); - let fs = { + let (fs, path) = { let mut state = state.borrow_mut(); - state + let path = state .borrow_mut::<P>() .check_write_with_api_name(&path, Some("node:fs.lchown"))?; - state.borrow::<FileSystemRc>().clone() + (state.borrow::<FileSystemRc>().clone(), path) }; fs.lchown_async(path, uid, gid).await?; Ok(()) |
