Skip to content

Commit 2de4faa

Browse files
authored
refactor: improve node permission checks (denoland#26028)
Does less work when requesting permissions with `-A`
1 parent f288730 commit 2de4faa

File tree

11 files changed

+214
-128
lines changed

11 files changed

+214
-128
lines changed

cli/npm/byonm.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22

3+
use std::borrow::Cow;
34
use std::path::Path;
45
use std::path::PathBuf;
56
use std::sync::Arc;
@@ -32,18 +33,19 @@ pub type CliByonmNpmResolver = ByonmNpmResolver<CliDenoResolverFs>;
3233
struct CliByonmWrapper(Arc<CliByonmNpmResolver>);
3334

3435
impl NodeRequireResolver for CliByonmWrapper {
35-
fn ensure_read_permission(
36+
fn ensure_read_permission<'a>(
3637
&self,
3738
permissions: &mut dyn NodePermissions,
38-
path: &Path,
39-
) -> Result<(), AnyError> {
39+
path: &'a Path,
40+
) -> Result<Cow<'a, Path>, AnyError> {
4041
if !path
4142
.components()
4243
.any(|c| c.as_os_str().to_ascii_lowercase() == "node_modules")
4344
{
44-
_ = permissions.check_read_path(path)?;
45+
permissions.check_read_path(path)
46+
} else {
47+
Ok(Cow::Borrowed(path))
4548
}
46-
Ok(())
4749
}
4850
}
4951

cli/npm/managed/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22

3+
use std::borrow::Cow;
34
use std::path::Path;
45
use std::path::PathBuf;
56
use std::sync::Arc;
@@ -593,11 +594,11 @@ impl NpmResolver for ManagedCliNpmResolver {
593594
}
594595

595596
impl NodeRequireResolver for ManagedCliNpmResolver {
596-
fn ensure_read_permission(
597+
fn ensure_read_permission<'a>(
597598
&self,
598599
permissions: &mut dyn NodePermissions,
599-
path: &Path,
600-
) -> Result<(), AnyError> {
600+
path: &'a Path,
601+
) -> Result<Cow<'a, Path>, AnyError> {
601602
self.fs_resolver.ensure_read_permission(permissions, path)
602603
}
603604
}

cli/npm/managed/resolvers/common.rs

+23-17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod bin_entries;
44
pub mod lifecycle_scripts;
55

6+
use std::borrow::Cow;
67
use std::collections::HashMap;
78
use std::io::ErrorKind;
89
use std::path::Path;
@@ -62,11 +63,12 @@ pub trait NpmPackageFsResolver: Send + Sync {
6263

6364
async fn cache_packages(&self) -> Result<(), AnyError>;
6465

65-
fn ensure_read_permission(
66+
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
67+
fn ensure_read_permission<'a>(
6668
&self,
6769
permissions: &mut dyn NodePermissions,
68-
path: &Path,
69-
) -> Result<(), AnyError>;
70+
path: &'a Path,
71+
) -> Result<Cow<'a, Path>, AnyError>;
7072
}
7173

7274
#[derive(Debug)]
@@ -85,11 +87,15 @@ impl RegistryReadPermissionChecker {
8587
}
8688
}
8789

88-
pub fn ensure_registry_read_permission(
90+
pub fn ensure_registry_read_permission<'a>(
8991
&self,
9092
permissions: &mut dyn NodePermissions,
91-
path: &Path,
92-
) -> Result<(), AnyError> {
93+
path: &'a Path,
94+
) -> Result<Cow<'a, Path>, AnyError> {
95+
if permissions.query_read_all() {
96+
return Ok(Cow::Borrowed(path)); // skip permissions checks below
97+
}
98+
9399
// allow reading if it's in the node_modules
94100
let is_path_in_node_modules = path.starts_with(&self.registry_path)
95101
&& path
@@ -118,20 +124,20 @@ impl RegistryReadPermissionChecker {
118124
},
119125
}
120126
};
121-
let Some(registry_path_canon) = canonicalize(&self.registry_path)? else {
122-
return Ok(()); // not exists, allow reading
123-
};
124-
let Some(path_canon) = canonicalize(path)? else {
125-
return Ok(()); // not exists, allow reading
126-
};
127-
128-
if path_canon.starts_with(registry_path_canon) {
129-
return Ok(());
127+
if let Some(registry_path_canon) = canonicalize(&self.registry_path)? {
128+
if let Some(path_canon) = canonicalize(path)? {
129+
if path_canon.starts_with(registry_path_canon) {
130+
return Ok(Cow::Owned(path_canon));
131+
}
132+
} else if path.starts_with(registry_path_canon)
133+
|| path.starts_with(&self.registry_path)
134+
{
135+
return Ok(Cow::Borrowed(path));
136+
}
130137
}
131138
}
132139

133-
_ = permissions.check_read_path(path)?;
134-
Ok(())
140+
permissions.check_read_path(path)
135141
}
136142
}
137143

cli/npm/managed/resolvers/global.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,11 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver {
183183
Ok(())
184184
}
185185

186-
fn ensure_read_permission(
186+
fn ensure_read_permission<'a>(
187187
&self,
188188
permissions: &mut dyn NodePermissions,
189-
path: &Path,
190-
) -> Result<(), AnyError> {
189+
path: &'a Path,
190+
) -> Result<Cow<'a, Path>, AnyError> {
191191
self
192192
.registry_read_permission_checker
193193
.ensure_registry_read_permission(permissions, path)

cli/npm/managed/resolvers/local.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,11 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver {
257257
.await
258258
}
259259

260-
fn ensure_read_permission(
260+
fn ensure_read_permission<'a>(
261261
&self,
262262
permissions: &mut dyn NodePermissions,
263-
path: &Path,
264-
) -> Result<(), AnyError> {
263+
path: &'a Path,
264+
) -> Result<Cow<'a, Path>, AnyError> {
265265
self
266266
.registry_read_permission_checker
267267
.ensure_registry_read_permission(permissions, path)

ext/node/lib.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub trait NodePermissions {
6666
&mut self,
6767
path: &'a Path,
6868
) -> Result<Cow<'a, Path>, AnyError>;
69+
fn query_read_all(&mut self) -> bool;
6970
fn check_sys(&mut self, kind: &str, api_name: &str) -> Result<(), AnyError>;
7071
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
7172
fn check_write_with_api_name(
@@ -103,6 +104,10 @@ impl NodePermissions for deno_permissions::PermissionsContainer {
103104
deno_permissions::PermissionsContainer::check_read_path(self, path, None)
104105
}
105106

107+
fn query_read_all(&mut self) -> bool {
108+
deno_permissions::PermissionsContainer::query_read_all(self)
109+
}
110+
106111
#[inline(always)]
107112
fn check_write_with_api_name(
108113
&mut self,
@@ -124,11 +129,12 @@ pub type NodeRequireResolverRc =
124129
deno_fs::sync::MaybeArc<dyn NodeRequireResolver>;
125130

126131
pub trait NodeRequireResolver: std::fmt::Debug + MaybeSend + MaybeSync {
127-
fn ensure_read_permission(
132+
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
133+
fn ensure_read_permission<'a>(
128134
&self,
129135
permissions: &mut dyn NodePermissions,
130-
path: &Path,
131-
) -> Result<(), AnyError>;
136+
path: &'a Path,
137+
) -> Result<Cow<'a, Path>, AnyError>;
132138
}
133139

134140
pub static NODE_ENV_VAR_ALLOWLIST: Lazy<HashSet<String>> = Lazy::new(|| {

ext/node/ops/require.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use deno_path_util::normalize_path;
1515
use node_resolver::NodeModuleKind;
1616
use node_resolver::NodeResolutionMode;
1717
use node_resolver::REQUIRE_CONDITIONS;
18+
use std::borrow::Cow;
1819
use std::cell::RefCell;
1920
use std::path::Path;
2021
use std::path::PathBuf;
@@ -25,10 +26,11 @@ use crate::NodeRequireResolverRc;
2526
use crate::NodeResolverRc;
2627
use crate::NpmResolverRc;
2728

28-
fn ensure_read_permission<P>(
29+
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
30+
fn ensure_read_permission<'a, P>(
2931
state: &mut OpState,
30-
file_path: &Path,
31-
) -> Result<(), AnyError>
32+
file_path: &'a Path,
33+
) -> Result<Cow<'a, Path>, AnyError>
3234
where
3335
P: NodePermissions + 'static,
3436
{
@@ -107,7 +109,7 @@ where
107109
deno_path_util::normalize_path(current_dir.join(from))
108110
};
109111

110-
ensure_read_permission::<P>(state, &from)?;
112+
let from = ensure_read_permission::<P>(state, &from)?;
111113

112114
if cfg!(windows) {
113115
// return root node_modules when path is 'D:\\'.
@@ -129,7 +131,7 @@ where
129131
}
130132

131133
let mut paths = Vec::with_capacity(from.components().count());
132-
let mut current_path = from.as_path();
134+
let mut current_path = from.as_ref();
133135
let mut maybe_parent = Some(current_path);
134136
while let Some(parent) = maybe_parent {
135137
if !parent.ends_with("node_modules") {
@@ -267,7 +269,7 @@ where
267269
P: NodePermissions + 'static,
268270
{
269271
let path = PathBuf::from(path);
270-
ensure_read_permission::<P>(state, &path)?;
272+
let path = ensure_read_permission::<P>(state, &path)?;
271273
let fs = state.borrow::<FileSystemRc>();
272274
if let Ok(metadata) = fs.stat_sync(&path) {
273275
if metadata.is_file {
@@ -290,7 +292,7 @@ where
290292
P: NodePermissions + 'static,
291293
{
292294
let path = PathBuf::from(request);
293-
ensure_read_permission::<P>(state, &path)?;
295+
let path = ensure_read_permission::<P>(state, &path)?;
294296
let fs = state.borrow::<FileSystemRc>();
295297
let canonicalized_path =
296298
deno_core::strip_unc_prefix(fs.realpath_sync(&path)?);
@@ -362,7 +364,7 @@ where
362364
if parent_id == "<repl>" || parent_id == "internal/preload" {
363365
let fs = state.borrow::<FileSystemRc>();
364366
if let Ok(cwd) = fs.cwd() {
365-
ensure_read_permission::<P>(state, &cwd)?;
367+
let cwd = ensure_read_permission::<P>(state, &cwd)?;
366368
return Ok(Some(cwd.to_string_lossy().into_owned()));
367369
}
368370
}
@@ -443,7 +445,7 @@ where
443445
P: NodePermissions + 'static,
444446
{
445447
let file_path = PathBuf::from(file_path);
446-
ensure_read_permission::<P>(state, &file_path)?;
448+
let file_path = ensure_read_permission::<P>(state, &file_path)?;
447449
let fs = state.borrow::<FileSystemRc>();
448450
Ok(fs.read_text_file_lossy_sync(&file_path, None)?)
449451
}
@@ -528,7 +530,7 @@ where
528530
P: NodePermissions + 'static,
529531
{
530532
let filename = PathBuf::from(filename);
531-
ensure_read_permission::<P>(state, filename.parent().unwrap())?;
533+
// permissions: allow reading the closest package.json files
532534
let node_resolver = state.borrow::<NodeResolverRc>().clone();
533535
node_resolver
534536
.get_closest_package_json_from_path(&filename)
@@ -567,7 +569,7 @@ where
567569
P: NodePermissions + 'static,
568570
{
569571
let referrer_path = PathBuf::from(&referrer_filename);
570-
ensure_read_permission::<P>(state, &referrer_path)?;
572+
let referrer_path = ensure_read_permission::<P>(state, &referrer_path)?;
571573
let node_resolver = state.borrow::<NodeResolverRc>();
572574
let Some(pkg) =
573575
node_resolver.get_closest_package_json_from_path(&referrer_path)?

ext/node/ops/worker_threads.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ use deno_core::url::Url;
77
use deno_core::OpState;
88
use deno_fs::FileSystemRc;
99
use node_resolver::NodeResolution;
10+
use std::borrow::Cow;
1011
use std::path::Path;
1112
use std::path::PathBuf;
1213

1314
use crate::NodePermissions;
1415
use crate::NodeRequireResolverRc;
1516
use crate::NodeResolverRc;
1617

17-
fn ensure_read_permission<P>(
18+
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
19+
fn ensure_read_permission<'a, P>(
1820
state: &mut OpState,
19-
file_path: &Path,
20-
) -> Result<(), AnyError>
21+
file_path: &'a Path,
22+
) -> Result<Cow<'a, Path>, AnyError>
2123
where
2224
P: NodePermissions + 'static,
2325
{
@@ -47,7 +49,7 @@ where
4749
"Relative path entries must start with '.' or '..'",
4850
));
4951
}
50-
ensure_read_permission::<P>(state, &path)?;
52+
let path = ensure_read_permission::<P>(state, &path)?;
5153
let fs = state.borrow::<FileSystemRc>();
5254
let canonicalized_path =
5355
deno_core::strip_unc_prefix(fs.realpath_sync(&path)?);
@@ -57,7 +59,7 @@ where
5759
let url_path = url
5860
.to_file_path()
5961
.map_err(|e| generic_error(format!("URL to Path-String: {:#?}", e)))?;
60-
ensure_read_permission::<P>(state, &url_path)?;
62+
let url_path = ensure_read_permission::<P>(state, &url_path)?;
6163
let fs = state.borrow::<FileSystemRc>();
6264
if !fs.exists_sync(&url_path) {
6365
return Err(generic_error(format!("File not found [{:?}]", url_path)));

0 commit comments

Comments
 (0)