diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/permissions.rs | 30 | ||||
-rw-r--r-- | runtime/permissions/lib.rs | 87 | ||||
-rw-r--r-- | runtime/snapshot.rs | 12 |
3 files changed, 128 insertions, 1 deletions
diff --git a/runtime/permissions.rs b/runtime/permissions.rs index ccd0d3254..edd03e1d5 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -1,9 +1,11 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::path::Path; use deno_core::error::AnyError; use deno_core::url::Url; +pub use deno_io::fs::FsError; pub use deno_permissions::create_child_permissions; pub use deno_permissions::parse_sys_kind; pub use deno_permissions::set_prompt_callbacks; @@ -142,6 +144,34 @@ impl deno_websocket::WebSocketPermissions for PermissionsContainer { } impl deno_fs::FsPermissions for PermissionsContainer { + fn check_open<'a>( + &mut self, + resolved: bool, + read: bool, + write: bool, + path: &'a Path, + api_name: &str, + ) -> Result<Cow<'a, Path>, FsError> { + if resolved { + self.check_special_file(path, api_name).map_err(|_| { + std::io::Error::from(std::io::ErrorKind::PermissionDenied) + })?; + return Ok(Cow::Borrowed(path)); + } + + // If somehow read or write aren't specified, use read + let read = read || !write; + if read { + deno_fs::FsPermissions::check_read(self, path, api_name) + .map_err(|_| FsError::PermissionDenied("read"))?; + } + if write { + deno_fs::FsPermissions::check_write(self, path, api_name) + .map_err(|_| FsError::PermissionDenied("write"))?; + } + Ok(Cow::Borrowed(path)) + } + fn check_read( &mut self, path: &Path, diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index 1b00dc2c7..1ac8779af 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -21,6 +21,7 @@ use fqdn::FQDN; use once_cell::sync::Lazy; use std::borrow::Cow; use std::collections::HashSet; +use std::ffi::OsStr; use std::fmt; use std::fmt::Debug; use std::hash::Hash; @@ -1642,6 +1643,91 @@ impl PermissionsContainer { } #[inline(always)] + pub fn check_sys_all(&mut self) -> Result<(), AnyError> { + self.0.lock().sys.check_all() + } + + #[inline(always)] + pub fn check_ffi_all(&mut self) -> Result<(), AnyError> { + self.0.lock().ffi.check_all() + } + + /// This checks to see if the allow-all flag was passed, not whether all + /// permissions are enabled! + #[inline(always)] + pub fn check_was_allow_all_flag_passed(&mut self) -> Result<(), AnyError> { + self.0.lock().all.check() + } + + /// Checks special file access, returning the failed permission type if + /// not successful. + pub fn check_special_file( + &mut self, + path: &Path, + _api_name: &str, + ) -> Result<(), &'static str> { + let error_all = |_| "all"; + + // Safe files with no major additional side-effects. While there's a small risk of someone + // draining system entropy by just reading one of these files constantly, that's not really + // something we worry about as they already have --allow-read to /dev. + if cfg!(unix) + && (path == OsStr::new("/dev/random") + || path == OsStr::new("/dev/urandom") + || path == OsStr::new("/dev/zero") + || path == OsStr::new("/dev/null")) + { + return Ok(()); + } + + if cfg!(target_os = "linux") { + if path.starts_with("/dev") + || path.starts_with("/proc") + || path.starts_with("/sys") + { + if path.ends_with("/environ") { + self.check_env_all().map_err(|_| "env")?; + } else { + self.check_was_allow_all_flag_passed().map_err(error_all)?; + } + } + if path.starts_with("/etc") { + self.check_was_allow_all_flag_passed().map_err(error_all)?; + } + } else if cfg!(unix) { + if path.starts_with("/dev") { + self.check_was_allow_all_flag_passed().map_err(error_all)?; + } + if path.starts_with("/etc") { + self.check_was_allow_all_flag_passed().map_err(error_all)?; + } + if path.starts_with("/private/etc") { + self.check_was_allow_all_flag_passed().map_err(error_all)?; + } + } else if cfg!(target_os = "windows") { + fn is_normalized_windows_drive_path(path: &Path) -> bool { + let s = path.as_os_str().as_encoded_bytes(); + // \\?\X:\ + if s.len() < 7 { + false + } else if s.starts_with(br#"\\?\"#) { + s[4].is_ascii_alphabetic() && s[5] == b':' && s[6] == b'\\' + } else { + false + } + } + + // If this is a normalized drive path, accept it + if !is_normalized_windows_drive_path(path) { + self.check_was_allow_all_flag_passed().map_err(error_all)?; + } + } else { + unimplemented!() + } + Ok(()) + } + + #[inline(always)] pub fn check_net_url( &mut self, url: &Url, @@ -2795,7 +2881,6 @@ mod tests { fn test_check_fail() { set_prompter(Box::new(TestPrompter)); let mut perms = Permissions::none_with_prompt(); - let prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock(); prompt_value.set(false); diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs index 54652e1f1..d5122af84 100644 --- a/runtime/snapshot.rs +++ b/runtime/snapshot.rs @@ -10,6 +10,7 @@ use deno_core::snapshot::*; use deno_core::v8; use deno_core::Extension; use deno_http::DefaultHttpPropertyExtractor; +use deno_io::fs::FsError; use std::io::Write; use std::path::Path; use std::path::PathBuf; @@ -129,6 +130,17 @@ impl deno_net::NetPermissions for Permissions { } impl deno_fs::FsPermissions for Permissions { + fn check_open<'a>( + &mut self, + _resolved: bool, + _read: bool, + _write: bool, + _path: &'a Path, + _api_name: &str, + ) -> Result<std::borrow::Cow<'a, Path>, FsError> { + unreachable!("snapshotting!") + } + fn check_read( &mut self, _path: &Path, |