Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime): Allow embedders to perform additional access checks on file open #23208

Merged
merged 6 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -298,7 +298,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
Loading