Skip to content

Commit

Permalink
feat(runtime): Allow embedders to perform additional access checks on…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
mmastrac committed Apr 20, 2024
1 parent 365e1f4 commit 472a370
Show file tree
Hide file tree
Showing 17 changed files with 477 additions and 122 deletions.
2 changes: 1 addition & 1 deletion cli/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl CjsCodeAnalyzer for CliCjsCodeAnalyzer {
Some(source) => source,
None => self
.fs
.read_text_file_sync(&specifier.to_file_path().unwrap())?,
.read_text_file_sync(&specifier.to_file_path().unwrap(), None)?,
};
let analysis = self.inner_cjs_analysis(specifier, &source)?;
match analysis {
Expand Down
2 changes: 1 addition & 1 deletion cli/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ impl NpmModuleLoader {
let file_path = specifier.to_file_path().unwrap();
let code = self
.fs
.read_text_file_sync(&file_path)
.read_text_file_sync(&file_path, None)
.map_err(AnyError::from)
.with_context(|| {
if file_path.is_dir() {
Expand Down
12 changes: 8 additions & 4 deletions cli/standalone/file_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;

use deno_runtime::deno_fs::AccessCheckCb;
use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_fs::FsDirEntry;
use deno_runtime::deno_fs::FsFileType;
Expand Down Expand Up @@ -47,6 +48,7 @@ impl DenoCompileFileSystem {
create_new: false,
mode: None,
},
None,
&old_file_bytes,
)
}
Expand Down Expand Up @@ -75,22 +77,24 @@ impl FileSystem for DenoCompileFileSystem {
&self,
path: &Path,
options: OpenOptions,
access_check: Option<AccessCheckCb>,
) -> FsResult<Rc<dyn File>> {
if self.0.is_path_within(path) {
Ok(self.0.open_file(path)?)
} else {
RealFs.open_sync(path, options)
RealFs.open_sync(path, options, access_check)
}
}
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>> {
if self.0.is_path_within(&path) {
Ok(self.0.open_file(&path)?)
} else {
RealFs.open_async(path, options).await
RealFs.open_async(path, options, access_check).await
}
}

Expand Down
2 changes: 1 addition & 1 deletion cli/util/gitignore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl GitIgnoreTree {
});
let current = self
.fs
.read_text_file_sync(&dir_path.join(".gitignore"))
.read_text_file_sync(&dir_path.join(".gitignore"), None)
.ok()
.and_then(|text| {
let mut builder = ignore::gitignore::GitignoreBuilder::new(dir_path);
Expand Down
32 changes: 23 additions & 9 deletions ext/fs/in_memory_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use deno_io::fs::FsError;
use deno_io::fs::FsResult;
use deno_io::fs::FsStat;

use crate::interface::AccessCheckCb;
use crate::interface::FsDirEntry;
use crate::interface::FsFileType;
use crate::FileSystem;
Expand Down Expand Up @@ -48,6 +49,7 @@ impl InMemoryFs {
.write_file_sync(
&path,
OpenOptions::write(true, false, false, None),
None,
&text.into_bytes(),
)
.unwrap();
Expand Down Expand Up @@ -82,15 +84,17 @@ impl FileSystem for InMemoryFs {
&self,
_path: &Path,
_options: OpenOptions,
_access_check: Option<AccessCheckCb>,
) -> FsResult<Rc<dyn File>> {
Err(FsError::NotSupported)
}
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>> {
self.open_sync(&path, options)
self.open_sync(&path, options, access_check)
}

fn mkdir_sync(
Expand Down Expand Up @@ -350,6 +354,7 @@ impl FileSystem for InMemoryFs {
&self,
path: &Path,
options: OpenOptions,
_access_check: Option<AccessCheckCb>,
data: &[u8],
) -> FsResult<()> {
let path = normalize_path(path);
Expand Down Expand Up @@ -397,16 +402,21 @@ impl FileSystem for InMemoryFs {
}
}

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<()> {
self.write_file_sync(&path, options, &data)
self.write_file_sync(&path, options, access_check, &data)
}

fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> {
fn read_file_sync(
&self,
path: &Path,
_access_check: Option<AccessCheckCb>,
) -> FsResult<Vec<u8>> {
let entry = self.get_entry(path);
match entry {
Some(entry) => match &*entry {
Expand All @@ -419,7 +429,11 @@ impl FileSystem for InMemoryFs {
None => Err(FsError::Io(Error::new(ErrorKind::NotFound, "Not found"))),
}
}
async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
self.read_file_sync(&path)
async fn read_file_async<'a>(
&'a self,
path: PathBuf,
access_check: Option<AccessCheckCb<'a>>,
) -> FsResult<Vec<u8>> {
self.read_file_sync(&path, access_check)
}
}
67 changes: 53 additions & 14 deletions ext/fs/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ pub struct FsDirEntry {
#[allow(clippy::disallowed_types)]
pub type FileSystemRc = crate::sync::MaybeArc<dyn FileSystem>;

pub trait AccessCheckFn:
for<'a> FnMut(
bool,
&'a Path,
&'a OpenOptions,
) -> FsResult<std::borrow::Cow<'a, Path>>
{
}
impl<T> AccessCheckFn for T where
T: for<'a> FnMut(
bool,
&'a Path,
&'a OpenOptions,
) -> FsResult<std::borrow::Cow<'a, Path>>
{
}

pub type AccessCheckCb<'a> = &'a mut (dyn AccessCheckFn + 'a);

#[async_trait::async_trait(?Send)]
pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
fn cwd(&self) -> FsResult<PathBuf>;
Expand All @@ -91,11 +110,13 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
&self,
path: &Path,
options: OpenOptions,
access_check: Option<AccessCheckCb>,
) -> FsResult<Rc<dyn 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>>;

fn mkdir_sync(&self, path: &Path, recursive: bool, mode: u32)
Expand Down Expand Up @@ -202,38 +223,48 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
&self,
path: &Path,
options: OpenOptions,
access_check: Option<AccessCheckCb>,
data: &[u8],
) -> FsResult<()> {
let file = self.open_sync(path, options)?;
let file = self.open_sync(path, options, access_check)?;
if let Some(mode) = options.mode {
file.clone().chmod_sync(mode)?;
}
file.write_all_sync(data)?;
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 file = self.open_async(path, options).await?;
let file = self.open_async(path, options, access_check).await?;
if let Some(mode) = options.mode {
file.clone().chmod_async(mode).await?;
}
file.write_all(data.into()).await?;
Ok(())
}

fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> {
fn read_file_sync(
&self,
path: &Path,
access_check: Option<AccessCheckCb>,
) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
let file = self.open_sync(path, options)?;
let file = self.open_sync(path, options, access_check)?;
let buf = file.read_all_sync()?;
Ok(buf)
}
async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
async fn read_file_async<'a>(
&'a self,
path: PathBuf,
access_check: Option<AccessCheckCb<'a>>,
) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
let file = self.open_async(path, options).await?;
let file = self.open_async(path, options, access_check).await?;
let buf = file.read_all_async().await?;
Ok(buf)
}
Expand All @@ -253,14 +284,22 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
self.stat_sync(path).is_ok()
}

fn read_text_file_sync(&self, path: &Path) -> FsResult<String> {
let buf = self.read_file_sync(path)?;
fn read_text_file_sync(
&self,
path: &Path,
access_check: Option<AccessCheckCb>,
) -> FsResult<String> {
let buf = self.read_file_sync(path, access_check)?;
String::from_utf8(buf).map_err(|err| {
std::io::Error::new(std::io::ErrorKind::InvalidData, err).into()
})
}
async fn read_text_file_async(&self, path: PathBuf) -> FsResult<String> {
let buf = self.read_file_async(path).await?;
async fn read_text_file_async<'a>(
&'a self,
path: PathBuf,
access_check: Option<AccessCheckCb<'a>>,
) -> FsResult<String> {
let buf = self.read_file_async(path, access_check).await?;
String::from_utf8(buf).map_err(|err| {
std::io::Error::new(std::io::ErrorKind::InvalidData, err).into()
})
Expand Down
34 changes: 23 additions & 11 deletions ext/fs/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod std_fs;
pub mod sync;

pub use crate::in_memory_fs::InMemoryFs;
pub use crate::interface::AccessCheckCb;
pub use crate::interface::AccessCheckFn;
pub use crate::interface::FileSystem;
pub use crate::interface::FileSystemRc;
pub use crate::interface::FsDirEntry;
Expand All @@ -20,9 +22,18 @@ use crate::ops::*;

use deno_core::error::AnyError;
use deno_core::OpState;
use deno_io::fs::FsError;
use std::path::Path;

pub trait FsPermissions {
pub trait FsPermissions: Send + Sync {
fn check_open<'a>(
&mut self,
resolved: bool,
read: bool,
write: bool,
path: &'a Path,
api_name: &str,
) -> Result<std::borrow::Cow<'a, Path>, FsError>;
fn check_read(&mut self, path: &Path, api_name: &str)
-> Result<(), AnyError>;
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
Expand Down Expand Up @@ -50,19 +61,20 @@ pub trait FsPermissions {
api_name: &str,
) -> Result<(), AnyError>;

fn check(
fn check<'a>(
&mut self,
resolved: bool,
open_options: &OpenOptions,
path: &Path,
path: &'a Path,
api_name: &str,
) -> Result<(), AnyError> {
if open_options.read {
self.check_read(path, api_name)?;
}
if open_options.write || open_options.append {
self.check_write(path, api_name)?;
}
Ok(())
) -> Result<std::borrow::Cow<'a, Path>, FsError> {
self.check_open(
resolved,
open_options.read,
open_options.write || open_options.append,
path,
api_name,
)
}
}

Expand Down

0 comments on commit 472a370

Please sign in to comment.