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/std_fs.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/std_fs.rs')
-rw-r--r-- | ext/fs/std_fs.rs | 134 |
1 files changed, 111 insertions, 23 deletions
diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs index 332866e45..1176697de 100644 --- a/ext/fs/std_fs.rs +++ b/ext/fs/std_fs.rs @@ -2,27 +2,29 @@ #![allow(clippy::disallowed_methods)] +use std::env::current_dir; use std::fs; use std::io; +use std::io::Read; use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; +use deno_core::normalize_path; use deno_core::unsync::spawn_blocking; use deno_io::fs::File; +use deno_io::fs::FsError; use deno_io::fs::FsResult; use deno_io::fs::FsStat; use deno_io::StdFileResourceInner; +use crate::interface::AccessCheckCb; use crate::interface::FsDirEntry; use crate::interface::FsFileType; use crate::FileSystem; use crate::OpenOptions; -#[cfg(not(unix))] -use deno_io::fs::FsError; - #[derive(Debug, Clone)] pub struct RealFs; @@ -80,18 +82,18 @@ impl FileSystem for RealFs { &self, path: &Path, options: OpenOptions, + access_check: Option<AccessCheckCb>, ) -> FsResult<Rc<dyn File>> { - let opts = open_options(options); - let std_file = opts.open(path)?; + let std_file = open_with_access_check(options, path, access_check)?; Ok(Rc::new(StdFileResourceInner::file(std_file))) } - async fn open_async( - &self, + async fn open_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, ) -> FsResult<Rc<dyn File>> { - let opts = open_options(options); - let std_file = spawn_blocking(move || opts.open(path)).await??; + let std_file = open_with_access_check(options, &path, access_check)?; Ok(Rc::new(StdFileResourceInner::file(std_file))) } @@ -276,10 +278,10 @@ impl FileSystem for RealFs { &self, path: &Path, options: OpenOptions, + access_check: Option<AccessCheckCb>, data: &[u8], ) -> FsResult<()> { - let opts = open_options(options); - let mut file = opts.open(path)?; + let mut file = open_with_access_check(options, path, access_check)?; #[cfg(unix)] if let Some(mode) = options.mode { use std::os::unix::fs::PermissionsExt; @@ -289,15 +291,15 @@ impl FileSystem for RealFs { Ok(()) } - async fn write_file_async( - &self, + async fn write_file_async<'a>( + &'a self, path: PathBuf, options: OpenOptions, + access_check: Option<AccessCheckCb<'a>>, data: Vec<u8>, ) -> FsResult<()> { + let mut file = open_with_access_check(options, &path, access_check)?; spawn_blocking(move || { - let opts = open_options(options); - let mut file = opts.open(path)?; #[cfg(unix)] if let Some(mode) = options.mode { use std::os::unix::fs::PermissionsExt; @@ -309,13 +311,43 @@ impl FileSystem for RealFs { .await? } - fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> { - fs::read(path).map_err(Into::into) - } - async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> { - spawn_blocking(move || fs::read(path)) - .await? - .map_err(Into::into) + fn read_file_sync( + &self, + path: &Path, + access_check: Option<AccessCheckCb>, + ) -> FsResult<Vec<u8>> { + let mut file = open_with_access_check( + OpenOptions { + read: true, + ..Default::default() + }, + path, + access_check, + )?; + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok(buf) + } + async fn read_file_async<'a>( + &'a self, + path: PathBuf, + access_check: Option<AccessCheckCb<'a>>, + ) -> FsResult<Vec<u8>> { + let mut file = open_with_access_check( + OpenOptions { + read: true, + ..Default::default() + }, + &path, + access_check, + )?; + spawn_blocking(move || { + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok::<_, FsError>(buf) + }) + .await? + .map_err(Into::into) } } @@ -410,7 +442,6 @@ fn copy_file(from: &Path, to: &Path) -> FsResult<()> { use libc::stat; use libc::unlink; use std::ffi::CString; - use std::io::Read; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::PermissionsExt; @@ -845,3 +876,60 @@ fn open_options(options: OpenOptions) -> fs::OpenOptions { open_options.create_new(options.create_new); open_options } + +#[inline(always)] +fn open_with_access_check( + options: OpenOptions, + path: &Path, + access_check: Option<AccessCheckCb>, +) -> FsResult<std::fs::File> { + if let Some(access_check) = access_check { + let path = if path.is_absolute() { + normalize_path(path) + } else { + let cwd = current_dir()?; + normalize_path(cwd.join(path)) + }; + (*access_check)(false, &path, &options)?; + // On Linux, /proc may contain magic links that we don't want to resolve + let needs_canonicalization = + !cfg!(target_os = "linux") || path.starts_with("/proc"); + let path = if needs_canonicalization { + match path.canonicalize() { + Ok(path) => path, + Err(_) => { + if let (Some(parent), Some(filename)) = + (path.parent(), path.file_name()) + { + parent.canonicalize()?.join(filename) + } else { + return Err(std::io::ErrorKind::NotFound.into()); + } + } + } + } else { + path + }; + + (*access_check)(true, &path, &options)?; + + // For windows + #[allow(unused_mut)] + let mut opts: fs::OpenOptions = open_options(options); + #[cfg(unix)] + { + // Don't follow symlinks on open -- we must always pass fully-resolved files + // with the exception of /proc/ which is too special, and /dev/std* which might point to + // proc. + use std::os::unix::fs::OpenOptionsExt; + if needs_canonicalization { + opts.custom_flags(libc::O_NOFOLLOW); + } + } + + Ok(opts.open(&path)?) + } else { + let opts = open_options(options); + Ok(opts.open(path)?) + } +} |