summaryrefslogtreecommitdiff
path: root/ext/fs/ops.rs
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2024-04-19 18:12:03 -0600
committerGitHub <noreply@github.com>2024-04-19 18:12:03 -0600
commit472a37064071c66cd1311cdea2e78de8d2bc0641 (patch)
tree94459f249eee0429480e2cea6ac37319e27de41d /ext/fs/ops.rs
parent365e1f48f7059f94d4eeb8f5ba8b3949b686b355 (diff)
feat(runtime): Allow embedders to perform additional access checks on file open (#23208)
Embedders may have special requirements around file opening, so we add a new `check_open` permission check that is called as part of the file open process.
Diffstat (limited to 'ext/fs/ops.rs')
-rw-r--r--ext/fs/ops.rs153
1 files changed, 103 insertions, 50 deletions
diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs
index d688f619e..3ff32bd8f 100644
--- a/ext/fs/ops.rs
+++ b/ext/fs/ops.rs
@@ -28,12 +28,55 @@ use rand::Rng;
use serde::Serialize;
use crate::check_unstable;
+use crate::interface::AccessCheckFn;
use crate::interface::FileSystemRc;
use crate::interface::FsDirEntry;
use crate::interface::FsFileType;
use crate::FsPermissions;
use crate::OpenOptions;
+fn sync_permission_check<'a, P: FsPermissions + 'static>(
+ permissions: &'a mut P,
+ api_name: &'static str,
+) -> impl AccessCheckFn + 'a {
+ move |resolved, path, options| {
+ permissions.check(resolved, options, path, api_name)
+ }
+}
+
+fn async_permission_check<P: FsPermissions + 'static>(
+ state: Rc<RefCell<OpState>>,
+ api_name: &'static str,
+) -> impl AccessCheckFn {
+ move |resolved, path, options| {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check(resolved, options, path, api_name)
+ }
+}
+
+fn map_permission_error(
+ operation: &'static str,
+ error: FsError,
+ path: &Path,
+) -> AnyError {
+ match error {
+ FsError::PermissionDenied(err) => {
+ let path = format!("{path:?}");
+ let (path, truncated) = if path.len() > 1024 {
+ (&path[0..1024], "...(truncated)")
+ } else {
+ (path.as_str(), "")
+ };
+ custom_error("PermissionDenied", format!("Requires {err} access to {path}{truncated}, run again with the --allow-{err} flag"))
+ }
+ err => Err::<(), _>(err)
+ .context_path(operation, path)
+ .err()
+ .unwrap(),
+ }
+}
+
#[op2]
#[string]
pub fn op_fs_cwd<P>(state: &mut OpState) -> Result<String, AnyError>
@@ -89,12 +132,14 @@ where
let path = PathBuf::from(path);
let options = options.unwrap_or_else(OpenOptions::read);
- let permissions = state.borrow_mut::<P>();
- permissions.check(&options, &path, "Deno.openSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
- let file = fs.open_sync(&path, options).context_path("open", &path)?;
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.openSync()");
+ let file = fs
+ .open_sync(&path, options, Some(&mut access_check))
+ .map_err(|error| map_permission_error("open", error, &path))?;
+ drop(access_check);
let rid = state
.resource_table
.add(FileResource::new(file, "fsFile".to_string()));
@@ -114,16 +159,13 @@ where
let path = PathBuf::from(path);
let options = options.unwrap_or_else(OpenOptions::read);
- let fs = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check(&options, &path, "Deno.open()")?;
- state.borrow::<FileSystemRc>().clone()
- };
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.open()");
+ let fs = state.borrow().borrow::<FileSystemRc>().clone();
let file = fs
- .open_async(path.clone(), options)
+ .open_async(path.clone(), options, Some(&mut access_check))
.await
- .context_path("open", &path)?;
+ .map_err(|error| map_permission_error("open", error, &path))?;
let rid = state
.borrow_mut()
@@ -961,11 +1003,10 @@ where
};
let mut rng = thread_rng();
-
const MAX_TRIES: u32 = 10;
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) {
+ match fs.open_sync(&path, open_opts, None) {
Ok(_) => return path_into_string(path.into_os_string()),
Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
continue;
@@ -1007,7 +1048,7 @@ where
const MAX_TRIES: u32 = 10;
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).await {
+ match fs.clone().open_async(path.clone(), open_opts, None).await {
Ok(_) => return path_into_string(path.into_os_string()),
Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
continue;
@@ -1150,14 +1191,13 @@ where
{
let path = PathBuf::from(path);
- let permissions = state.borrow_mut::<P>();
let options = OpenOptions::write(create, append, create_new, mode);
- permissions.check(&options, &path, "Deno.writeFileSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.writeFileSync()");
- fs.write_file_sync(&path, options, &data)
- .context_path("writefile", &path)?;
+ fs.write_file_sync(&path, options, Some(&mut access_check), &data)
+ .map_err(|error| map_permission_error("writefile", error, &path))?;
Ok(())
}
@@ -1181,16 +1221,21 @@ where
let options = OpenOptions::write(create, append, create_new, mode);
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.writeFile()");
let (fs, cancel_handle) = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check(&options, &path, "Deno.writeFile()")?;
+ let state = state.borrow_mut();
let cancel_handle = cancel_rid
.and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
(state.borrow::<FileSystemRc>().clone(), cancel_handle)
};
- let fut = fs.write_file_async(path.clone(), options, data.to_vec());
+ let fut = fs.write_file_async(
+ path.clone(),
+ options,
+ Some(&mut access_check),
+ data.to_vec(),
+ );
if let Some(cancel_handle) = cancel_handle {
let res = fut.or_cancel(cancel_handle).await;
@@ -1201,9 +1246,11 @@ where
}
};
- res?.context_path("writefile", &path)?;
+ res?.map_err(|error| map_permission_error("writefile", error, &path))?;
} else {
- fut.await.context_path("writefile", &path)?;
+ fut
+ .await
+ .map_err(|error| map_permission_error("writefile", error, &path))?;
}
Ok(())
@@ -1220,11 +1267,12 @@ where
{
let path = PathBuf::from(path);
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFileSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
- let buf = fs.read_file_sync(&path).context_path("readfile", &path)?;
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.readFileSync()");
+ let buf = fs
+ .read_file_sync(&path, Some(&mut access_check))
+ .map_err(|error| map_permission_error("readfile", error, &path))?;
Ok(buf.into())
}
@@ -1241,16 +1289,16 @@ where
{
let path = PathBuf::from(path);
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.readFile()");
let (fs, cancel_handle) = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFile()")?;
+ let state = state.borrow();
let cancel_handle = cancel_rid
.and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
(state.borrow::<FileSystemRc>().clone(), cancel_handle)
};
- let fut = fs.read_file_async(path.clone());
+ let fut = fs.read_file_async(path.clone(), Some(&mut access_check));
let buf = if let Some(cancel_handle) = cancel_handle {
let res = fut.or_cancel(cancel_handle).await;
@@ -1261,9 +1309,11 @@ where
}
};
- res?.context_path("readfile", &path)?
+ res?.map_err(|error| map_permission_error("readfile", error, &path))?
} else {
- fut.await.context_path("readfile", &path)?
+ fut
+ .await
+ .map_err(|error| map_permission_error("readfile", error, &path))?
};
Ok(buf.into())
@@ -1280,11 +1330,12 @@ where
{
let path = PathBuf::from(path);
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFileSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
- let buf = fs.read_file_sync(&path).context_path("readfile", &path)?;
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.readFileSync()");
+ let buf = fs
+ .read_file_sync(&path, Some(&mut access_check))
+ .map_err(|error| map_permission_error("readfile", error, &path))?;
Ok(string_from_utf8_lossy(buf))
}
@@ -1301,16 +1352,16 @@ where
{
let path = PathBuf::from(path);
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.readFile()");
let (fs, cancel_handle) = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFile()")?;
+ let state = state.borrow_mut();
let cancel_handle = cancel_rid
.and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
(state.borrow::<FileSystemRc>().clone(), cancel_handle)
};
- let fut = fs.read_file_async(path.clone());
+ let fut = fs.read_file_async(path.clone(), Some(&mut access_check));
let buf = if let Some(cancel_handle) = cancel_handle {
let res = fut.or_cancel(cancel_handle).await;
@@ -1321,9 +1372,11 @@ where
}
};
- res?.context_path("readfile", &path)?
+ res?.map_err(|error| map_permission_error("readfile", error, &path))?
} else {
- fut.await.context_path("readfile", &path)?
+ fut
+ .await
+ .map_err(|error| map_permission_error("readfile", error, &path))?
};
Ok(string_from_utf8_lossy(buf))