diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-04-19 18:12:03 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-19 18:12:03 -0600 |
commit | 472a37064071c66cd1311cdea2e78de8d2bc0641 (patch) | |
tree | 94459f249eee0429480e2cea6ac37319e27de41d /ext/fs/ops.rs | |
parent | 365e1f48f7059f94d4eeb8f5ba8b3949b686b355 (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.rs | 153 |
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)) |