diff --git a/cli/npm/byonm.rs b/cli/npm/byonm.rs index 02c2e6da81..fc095ab16f 100644 --- a/cli/npm/byonm.rs +++ b/cli/npm/byonm.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -32,18 +33,19 @@ pub type CliByonmNpmResolver = ByonmNpmResolver; struct CliByonmWrapper(Arc); impl NodeRequireResolver for CliByonmWrapper { - fn ensure_read_permission( + fn ensure_read_permission<'a>( &self, permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError> { + path: &'a Path, + ) -> Result, AnyError> { if !path .components() .any(|c| c.as_os_str().to_ascii_lowercase() == "node_modules") { - _ = permissions.check_read_path(path)?; + permissions.check_read_path(path) + } else { + Ok(Cow::Borrowed(path)) } - Ok(()) } } diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs index 225cd6c29c..ec50a9c65a 100644 --- a/cli/npm/managed/mod.rs +++ b/cli/npm/managed/mod.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -593,11 +594,11 @@ impl NpmResolver for ManagedCliNpmResolver { } impl NodeRequireResolver for ManagedCliNpmResolver { - fn ensure_read_permission( + fn ensure_read_permission<'a>( &self, permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError> { + path: &'a Path, + ) -> Result, AnyError> { self.fs_resolver.ensure_read_permission(permissions, path) } } diff --git a/cli/npm/managed/resolvers/common.rs b/cli/npm/managed/resolvers/common.rs index 8df4debc5c..867bb4168a 100644 --- a/cli/npm/managed/resolvers/common.rs +++ b/cli/npm/managed/resolvers/common.rs @@ -3,6 +3,7 @@ pub mod bin_entries; pub mod lifecycle_scripts; +use std::borrow::Cow; use std::collections::HashMap; use std::io::ErrorKind; use std::path::Path; @@ -62,11 +63,12 @@ pub trait NpmPackageFsResolver: Send + Sync { async fn cache_packages(&self) -> Result<(), AnyError>; - fn ensure_read_permission( + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn ensure_read_permission<'a>( &self, permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError>; + path: &'a Path, + ) -> Result, AnyError>; } #[derive(Debug)] @@ -85,11 +87,15 @@ impl RegistryReadPermissionChecker { } } - pub fn ensure_registry_read_permission( + pub fn ensure_registry_read_permission<'a>( &self, permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError> { + path: &'a Path, + ) -> Result, AnyError> { + if permissions.query_read_all() { + return Ok(Cow::Borrowed(path)); // skip permissions checks below + } + // allow reading if it's in the node_modules let is_path_in_node_modules = path.starts_with(&self.registry_path) && path @@ -118,20 +124,20 @@ impl RegistryReadPermissionChecker { }, } }; - let Some(registry_path_canon) = canonicalize(&self.registry_path)? else { - return Ok(()); // not exists, allow reading - }; - let Some(path_canon) = canonicalize(path)? else { - return Ok(()); // not exists, allow reading - }; - - if path_canon.starts_with(registry_path_canon) { - return Ok(()); + if let Some(registry_path_canon) = canonicalize(&self.registry_path)? { + if let Some(path_canon) = canonicalize(path)? { + if path_canon.starts_with(registry_path_canon) { + return Ok(Cow::Owned(path_canon)); + } + } else if path.starts_with(registry_path_canon) + || path.starts_with(&self.registry_path) + { + return Ok(Cow::Borrowed(path)); + } } } - _ = permissions.check_read_path(path)?; - Ok(()) + permissions.check_read_path(path) } } diff --git a/cli/npm/managed/resolvers/global.rs b/cli/npm/managed/resolvers/global.rs index ca5722c869..5be315e992 100644 --- a/cli/npm/managed/resolvers/global.rs +++ b/cli/npm/managed/resolvers/global.rs @@ -183,11 +183,11 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { Ok(()) } - fn ensure_read_permission( + fn ensure_read_permission<'a>( &self, permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError> { + path: &'a Path, + ) -> Result, AnyError> { self .registry_read_permission_checker .ensure_registry_read_permission(permissions, path) diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index 258c9bf3d5..63a972a431 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -257,11 +257,11 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { .await } - fn ensure_read_permission( + fn ensure_read_permission<'a>( &self, permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError> { + path: &'a Path, + ) -> Result, AnyError> { self .registry_read_permission_checker .ensure_registry_read_permission(permissions, path) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index d23c072042..03462f36fa 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -66,6 +66,7 @@ pub trait NodePermissions { &mut self, path: &'a Path, ) -> Result, AnyError>; + fn query_read_all(&mut self) -> bool; 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( @@ -103,6 +104,10 @@ impl NodePermissions for deno_permissions::PermissionsContainer { deno_permissions::PermissionsContainer::check_read_path(self, path, None) } + fn query_read_all(&mut self) -> bool { + deno_permissions::PermissionsContainer::query_read_all(self) + } + #[inline(always)] fn check_write_with_api_name( &mut self, @@ -124,11 +129,12 @@ pub type NodeRequireResolverRc = deno_fs::sync::MaybeArc; pub trait NodeRequireResolver: std::fmt::Debug + MaybeSend + MaybeSync { - fn ensure_read_permission( + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] + fn ensure_read_permission<'a>( &self, permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError>; + path: &'a Path, + ) -> Result, AnyError>; } pub static NODE_ENV_VAR_ALLOWLIST: Lazy> = Lazy::new(|| { diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs index 15667aae73..5473369815 100644 --- a/ext/node/ops/require.rs +++ b/ext/node/ops/require.rs @@ -15,6 +15,7 @@ use deno_path_util::normalize_path; use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionMode; use node_resolver::REQUIRE_CONDITIONS; +use std::borrow::Cow; use std::cell::RefCell; use std::path::Path; use std::path::PathBuf; @@ -25,10 +26,11 @@ use crate::NodeRequireResolverRc; use crate::NodeResolverRc; use crate::NpmResolverRc; -fn ensure_read_permission

( +#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] +fn ensure_read_permission<'a, P>( state: &mut OpState, - file_path: &Path, -) -> Result<(), AnyError> + file_path: &'a Path, +) -> Result, AnyError> where P: NodePermissions + 'static, { @@ -107,7 +109,7 @@ where deno_path_util::normalize_path(current_dir.join(from)) }; - ensure_read_permission::

(state, &from)?; + let from = ensure_read_permission::

(state, &from)?; if cfg!(windows) { // return root node_modules when path is 'D:\\'. @@ -129,7 +131,7 @@ where } let mut paths = Vec::with_capacity(from.components().count()); - let mut current_path = from.as_path(); + let mut current_path = from.as_ref(); let mut maybe_parent = Some(current_path); while let Some(parent) = maybe_parent { if !parent.ends_with("node_modules") { @@ -267,7 +269,7 @@ where P: NodePermissions + 'static, { let path = PathBuf::from(path); - ensure_read_permission::

(state, &path)?; + let path = ensure_read_permission::

(state, &path)?; let fs = state.borrow::(); if let Ok(metadata) = fs.stat_sync(&path) { if metadata.is_file { @@ -290,7 +292,7 @@ where P: NodePermissions + 'static, { let path = PathBuf::from(request); - ensure_read_permission::

(state, &path)?; + let path = ensure_read_permission::

(state, &path)?; let fs = state.borrow::(); let canonicalized_path = deno_core::strip_unc_prefix(fs.realpath_sync(&path)?); @@ -362,7 +364,7 @@ where if parent_id == "" || parent_id == "internal/preload" { let fs = state.borrow::(); if let Ok(cwd) = fs.cwd() { - ensure_read_permission::

(state, &cwd)?; + let cwd = ensure_read_permission::

(state, &cwd)?; return Ok(Some(cwd.to_string_lossy().into_owned())); } } @@ -443,7 +445,7 @@ where P: NodePermissions + 'static, { let file_path = PathBuf::from(file_path); - ensure_read_permission::

(state, &file_path)?; + let file_path = ensure_read_permission::

(state, &file_path)?; let fs = state.borrow::(); Ok(fs.read_text_file_lossy_sync(&file_path, None)?) } @@ -528,7 +530,7 @@ where P: NodePermissions + 'static, { let filename = PathBuf::from(filename); - ensure_read_permission::

(state, filename.parent().unwrap())?; + // permissions: allow reading the closest package.json files let node_resolver = state.borrow::().clone(); node_resolver .get_closest_package_json_from_path(&filename) @@ -567,7 +569,7 @@ where P: NodePermissions + 'static, { let referrer_path = PathBuf::from(&referrer_filename); - ensure_read_permission::

(state, &referrer_path)?; + let referrer_path = ensure_read_permission::

(state, &referrer_path)?; let node_resolver = state.borrow::(); let Some(pkg) = node_resolver.get_closest_package_json_from_path(&referrer_path)? diff --git a/ext/node/ops/worker_threads.rs b/ext/node/ops/worker_threads.rs index c7ea4c52c2..4c50092f28 100644 --- a/ext/node/ops/worker_threads.rs +++ b/ext/node/ops/worker_threads.rs @@ -7,6 +7,7 @@ use deno_core::url::Url; use deno_core::OpState; use deno_fs::FileSystemRc; use node_resolver::NodeResolution; +use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; @@ -14,10 +15,11 @@ use crate::NodePermissions; use crate::NodeRequireResolverRc; use crate::NodeResolverRc; -fn ensure_read_permission

( +#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] +fn ensure_read_permission<'a, P>( state: &mut OpState, - file_path: &Path, -) -> Result<(), AnyError> + file_path: &'a Path, +) -> Result, AnyError> where P: NodePermissions + 'static, { @@ -47,7 +49,7 @@ where "Relative path entries must start with '.' or '..'", )); } - ensure_read_permission::

(state, &path)?; + let path = ensure_read_permission::

(state, &path)?; let fs = state.borrow::(); let canonicalized_path = deno_core::strip_unc_prefix(fs.realpath_sync(&path)?); @@ -57,7 +59,7 @@ where let url_path = url .to_file_path() .map_err(|e| generic_error(format!("URL to Path-String: {:#?}", e)))?; - ensure_read_permission::

(state, &url_path)?; + let url_path = ensure_read_permission::

(state, &url_path)?; let fs = state.borrow::(); if !fs.exists_sync(&url_path) { return Err(generic_error(format!("File not found [{:?}]", url_path))); diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index efabd0b171..2904242dae 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -2285,6 +2285,11 @@ impl PermissionsContainer { self.inner.lock().read.check_all(Some(api_name)) } + #[inline(always)] + pub fn query_read_all(&self) -> bool { + self.inner.lock().read.query(None) == PermissionState::Granted + } + #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] #[inline(always)] pub fn check_write( @@ -2614,8 +2619,13 @@ impl PermissionsContainer { &self, path: Option<&str>, ) -> Result { + let inner = self.inner.lock(); + let permission = &inner.read; + if permission.is_allow_all() { + return Ok(PermissionState::Granted); + } Ok( - self.inner.lock().read.query( + permission.query( path .map(|path| { Result::<_, AnyError>::Ok( @@ -2633,8 +2643,13 @@ impl PermissionsContainer { &self, path: Option<&str>, ) -> Result { + let inner = self.inner.lock(); + let permission = &inner.write; + if permission.is_allow_all() { + return Ok(PermissionState::Granted); + } Ok( - self.inner.lock().write.query( + permission.query( path .map(|path| { Result::<_, AnyError>::Ok( @@ -2652,8 +2667,13 @@ impl PermissionsContainer { &self, host: Option<&str>, ) -> Result { + let inner = self.inner.lock(); + let permission = &inner.net; + if permission.is_allow_all() { + return Ok(PermissionState::Granted); + } Ok( - self.inner.lock().net.query( + permission.query( match host { None => None, Some(h) => Some(self.descriptor_parser.parse_net_descriptor(h)?), @@ -2665,7 +2685,12 @@ impl PermissionsContainer { #[inline(always)] pub fn query_env(&self, var: Option<&str>) -> PermissionState { - self.inner.lock().env.query(var) + let inner = self.inner.lock(); + let permission = &inner.env; + if permission.is_allow_all() { + return PermissionState::Granted; + } + permission.query(var) } #[inline(always)] @@ -2673,8 +2698,13 @@ impl PermissionsContainer { &self, kind: Option<&str>, ) -> Result { + let inner = self.inner.lock(); + let permission = &inner.sys; + if permission.is_allow_all() { + return Ok(PermissionState::Granted); + } Ok( - self.inner.lock().sys.query( + permission.query( kind .map(|kind| self.descriptor_parser.parse_sys_descriptor(kind)) .transpose()? @@ -2688,8 +2718,13 @@ impl PermissionsContainer { &self, cmd: Option<&str>, ) -> Result { + let inner = self.inner.lock(); + let permission = &inner.run; + if permission.is_allow_all() { + return Ok(PermissionState::Granted); + } Ok( - self.inner.lock().run.query( + permission.query( cmd .map(|request| self.descriptor_parser.parse_run_query(request)) .transpose()? @@ -2703,8 +2738,13 @@ impl PermissionsContainer { &self, path: Option<&str>, ) -> Result { + let inner = self.inner.lock(); + let permission = &inner.ffi; + if permission.is_allow_all() { + return Ok(PermissionState::Granted); + } Ok( - self.inner.lock().ffi.query( + permission.query( path .map(|path| { Result::<_, AnyError>::Ok( diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs index 456810e6a6..041132f971 100644 --- a/runtime/snapshot.rs +++ b/runtime/snapshot.rs @@ -97,6 +97,9 @@ impl deno_node::NodePermissions for Permissions { ) -> Result { unreachable!("snapshotting!") } + fn query_read_all(&mut self) -> bool { + unreachable!("snapshotting!") + } fn check_write_with_api_name( &mut self, _p: &str, diff --git a/tests/unit/permissions_test.ts b/tests/unit/permissions_test.ts index 82524d556c..f981b10fd6 100644 --- a/tests/unit/permissions_test.ts +++ b/tests/unit/permissions_test.ts @@ -20,54 +20,72 @@ Deno.test(function permissionInvalidNameSync() { }, TypeError); }); -Deno.test(async function permissionNetInvalidHost() { - await assertRejects(async () => { - await Deno.permissions.query({ name: "net", host: ":" }); - }, URIError); -}); +Deno.test( + { permissions: { net: [] } }, + async function permissionNetInvalidHost() { + await assertRejects(async () => { + await Deno.permissions.query({ name: "net", host: ":" }); + }, URIError); + }, +); -Deno.test(function permissionNetInvalidHostSync() { - assertThrows(() => { - Deno.permissions.querySync({ name: "net", host: ":" }); - }, URIError); -}); +Deno.test( + { permissions: { net: [] } }, + function permissionNetInvalidHostSync() { + assertThrows(() => { + Deno.permissions.querySync({ name: "net", host: ":" }); + }, URIError); + }, +); -Deno.test(async function permissionSysValidKind() { - await Deno.permissions.query({ name: "sys", kind: "loadavg" }); - await Deno.permissions.query({ name: "sys", kind: "osRelease" }); - await Deno.permissions.query({ name: "sys", kind: "osUptime" }); - await Deno.permissions.query({ name: "sys", kind: "networkInterfaces" }); - await Deno.permissions.query({ name: "sys", kind: "systemMemoryInfo" }); - await Deno.permissions.query({ name: "sys", kind: "hostname" }); - await Deno.permissions.query({ name: "sys", kind: "uid" }); - await Deno.permissions.query({ name: "sys", kind: "gid" }); - await Deno.permissions.query({ name: "sys", kind: "cpus" }); -}); +Deno.test( + { permissions: { sys: [] } }, + async function permissionSysValidKind() { + await Deno.permissions.query({ name: "sys", kind: "loadavg" }); + await Deno.permissions.query({ name: "sys", kind: "osRelease" }); + await Deno.permissions.query({ name: "sys", kind: "osUptime" }); + await Deno.permissions.query({ name: "sys", kind: "networkInterfaces" }); + await Deno.permissions.query({ name: "sys", kind: "systemMemoryInfo" }); + await Deno.permissions.query({ name: "sys", kind: "hostname" }); + await Deno.permissions.query({ name: "sys", kind: "uid" }); + await Deno.permissions.query({ name: "sys", kind: "gid" }); + await Deno.permissions.query({ name: "sys", kind: "cpus" }); + }, +); -Deno.test(function permissionSysValidKindSync() { - Deno.permissions.querySync({ name: "sys", kind: "loadavg" }); - Deno.permissions.querySync({ name: "sys", kind: "osRelease" }); - Deno.permissions.querySync({ name: "sys", kind: "networkInterfaces" }); - Deno.permissions.querySync({ name: "sys", kind: "systemMemoryInfo" }); - Deno.permissions.querySync({ name: "sys", kind: "hostname" }); - Deno.permissions.querySync({ name: "sys", kind: "uid" }); - Deno.permissions.querySync({ name: "sys", kind: "gid" }); - Deno.permissions.querySync({ name: "sys", kind: "cpus" }); -}); +Deno.test( + { permissions: { sys: [] } }, + function permissionSysValidKindSync() { + Deno.permissions.querySync({ name: "sys", kind: "loadavg" }); + Deno.permissions.querySync({ name: "sys", kind: "osRelease" }); + Deno.permissions.querySync({ name: "sys", kind: "networkInterfaces" }); + Deno.permissions.querySync({ name: "sys", kind: "systemMemoryInfo" }); + Deno.permissions.querySync({ name: "sys", kind: "hostname" }); + Deno.permissions.querySync({ name: "sys", kind: "uid" }); + Deno.permissions.querySync({ name: "sys", kind: "gid" }); + Deno.permissions.querySync({ name: "sys", kind: "cpus" }); + }, +); -Deno.test(async function permissionSysInvalidKind() { - await assertRejects(async () => { - // deno-lint-ignore no-explicit-any - await Deno.permissions.query({ name: "sys", kind: "abc" as any }); - }, TypeError); -}); +Deno.test( + { permissions: { sys: [] } }, + async function permissionSysInvalidKind() { + await assertRejects(async () => { + // deno-lint-ignore no-explicit-any + await Deno.permissions.query({ name: "sys", kind: "abc" as any }); + }, TypeError); + }, +); -Deno.test(function permissionSysInvalidKindSync() { - assertThrows(() => { - // deno-lint-ignore no-explicit-any - Deno.permissions.querySync({ name: "sys", kind: "abc" as any }); - }, TypeError); -}); +Deno.test( + { permissions: { sys: [] } }, + function permissionSysInvalidKindSync() { + assertThrows(() => { + // deno-lint-ignore no-explicit-any + Deno.permissions.querySync({ name: "sys", kind: "abc" as any }); + }, TypeError); + }, +); Deno.test(async function permissionQueryReturnsEventTarget() { const status = await Deno.permissions.query({ name: "read", path: "." }); @@ -134,29 +152,35 @@ Deno.test(function permissionStatusIllegalConstructor() { }); // Regression test for https://github.com/denoland/deno/issues/17020 -Deno.test(async function permissionURL() { - const path = new URL(".", import.meta.url); +Deno.test( + { permissions: { read: [], write: [], ffi: [], run: [] } }, + async function permissionURL() { + const path = new URL(".", import.meta.url); - await Deno.permissions.query({ name: "read", path }); - await Deno.permissions.query({ name: "write", path }); - await Deno.permissions.query({ name: "ffi", path }); - await Deno.permissions.query({ name: "run", command: path }); -}); + await Deno.permissions.query({ name: "read", path }); + await Deno.permissions.query({ name: "write", path }); + await Deno.permissions.query({ name: "ffi", path }); + await Deno.permissions.query({ name: "run", command: path }); + }, +); -Deno.test(function permissionURLSync() { - Deno.permissions.querySync({ - name: "read", - path: new URL(".", import.meta.url), - }); - Deno.permissions.querySync({ - name: "write", - path: new URL(".", import.meta.url), - }); - Deno.permissions.querySync({ - name: "run", - command: new URL(".", import.meta.url), - }); -}); +Deno.test( + { permissions: { read: [], write: [], ffi: [], run: [] } }, + function permissionURLSync() { + Deno.permissions.querySync({ + name: "read", + path: new URL(".", import.meta.url), + }); + Deno.permissions.querySync({ + name: "write", + path: new URL(".", import.meta.url), + }); + Deno.permissions.querySync({ + name: "run", + command: new URL(".", import.meta.url), + }); + }, +); Deno.test(async function permissionDescriptorValidation() { for (const value of [undefined, null, {}]) {