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

fix: do special file permission check for check_read_path #27989

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/fetch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bytes.workspace = true
data-url.workspace = true
deno_core.workspace = true
deno_error.workspace = true
deno_fs.workspace = true
deno_path_util.workspace = true
deno_permissions.workspace = true
deno_tls.workspace = true
Expand Down
50 changes: 43 additions & 7 deletions ext/fetch/fs_fetch_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@ use deno_core::url::Url;
use deno_core::CancelFuture;
use deno_core::OpState;
use deno_error::JsErrorBox;
use deno_fs::open_with_access_check;
use deno_fs::OpenOptions;
use deno_permissions::PermissionsContainer;
use http::StatusCode;
use http_body_util::BodyExt;
use tokio_util::io::ReaderStream;

use crate::CancelHandle;
use crate::CancelableResponseFuture;
use crate::FetchHandler;
use crate::FetchPermissions;

fn sync_permission_check<'a, P: FetchPermissions + 'static>(
permissions: &'a mut P,
api_name: &'static str,
) -> impl deno_fs::AccessCheckFn + 'a {
move |resolved, path, _options| {
permissions.check_read(resolved, path, api_name)
}
}

/// An implementation which tries to read file URLs from the file system via
/// tokio::fs.
Expand All @@ -25,25 +38,48 @@ pub struct FsFetchHandler;
impl FetchHandler for FsFetchHandler {
fn fetch_file(
&self,
_state: &mut OpState,
state: &mut OpState,
url: &Url,
) -> (CancelableResponseFuture, Option<Rc<CancelHandle>>) {
let mut access_check = sync_permission_check::<PermissionsContainer>(
state.borrow_mut(),
"fetch()",
);
let cancel_handle = CancelHandle::new_rc();
let path_result = url.to_file_path();
let path = match url.to_file_path() {
Ok(path) => path,
Err(_) => {
let fut = async move { Err::<_, _>(()) };
return (
fut
.map_err(move |_| super::FetchError::NetworkError)
.or_cancel(&cancel_handle)
.boxed_local(),
Some(cancel_handle),
);
}
};
let file = open_with_access_check(
OpenOptions {
read: true,
..Default::default()
},
&path,
Some(&mut access_check),
);
let response_fut = async move {
let path = path_result?;
let file = tokio::fs::File::open(path).map_err(|_| ()).await?;
let file = tokio::fs::File::from_std(file?);
let stream = ReaderStream::new(file)
.map_ok(hyper::body::Frame::data)
.map_err(JsErrorBox::from_err);

let body = http_body_util::StreamBody::new(stream).boxed();
let response = http::Response::builder()
.status(StatusCode::OK)
.body(body)
.map_err(|_| ())?;
Ok::<_, ()>(response)
.map_err(move |_| super::FetchError::NetworkError)?;
Ok::<_, _>(response)
}
.map_err(move |_| super::FetchError::NetworkError)
.or_cancel(&cancel_handle)
.boxed_local();

Expand Down
39 changes: 27 additions & 12 deletions ext/fetch/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_error::JsErrorBox;
use deno_path_util::url_from_file_path;
use deno_fs::FsError;
use deno_path_util::PathToUrlError;
use deno_permissions::PermissionCheckError;
use deno_tls::rustls::RootCertStore;
Expand Down Expand Up @@ -227,6 +227,20 @@ pub enum FetchError {
#[class(generic)]
#[error(transparent)]
Dns(hickory_resolver::ResolveError),
#[class("NotCapable")]
#[error("requires {0} access")]
NotCapable(&'static str),
}

impl From<deno_fs::FsError> for FetchError {
fn from(value: deno_fs::FsError) -> Self {
match value {
deno_fs::FsError::Io(_)
| deno_fs::FsError::FileBusy
| deno_fs::FsError::NotSupported => FetchError::NetworkError,
deno_fs::FsError::NotCapable(err) => FetchError::NotCapable(err),
}
}
}

pub type CancelableResponseFuture =
Expand Down Expand Up @@ -390,9 +404,10 @@ pub trait FetchPermissions {
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
fn check_read<'a>(
&mut self,
resolved: bool,
p: &'a Path,
api_name: &str,
) -> Result<Cow<'a, Path>, PermissionCheckError>;
) -> Result<Cow<'a, Path>, FsError>;
}

impl FetchPermissions for deno_permissions::PermissionsContainer {
Expand All @@ -408,14 +423,23 @@ impl FetchPermissions for deno_permissions::PermissionsContainer {
#[inline(always)]
fn check_read<'a>(
&mut self,
resolved: bool,
path: &'a Path,
api_name: &str,
) -> Result<Cow<'a, Path>, PermissionCheckError> {
) -> Result<Cow<'a, Path>, FsError> {
if resolved {
self
.check_special_file(path, api_name)
.map_err(FsError::NotCapable)?;
return Ok(Cow::Borrowed(path));
}

deno_permissions::PermissionsContainer::check_read_path(
self,
path,
Some(api_name),
)
.map_err(|_| FsError::NotCapable("read"))
}
}

Expand Down Expand Up @@ -449,18 +473,9 @@ where
let scheme = url.scheme();
let (request_rid, cancel_handle_rid) = match scheme {
"file" => {
let path = url.to_file_path().map_err(|_| FetchError::NetworkError)?;
let permissions = state.borrow_mut::<FP>();
let path = permissions.check_read(&path, "fetch()")?;
let url = match path {
Cow::Owned(path) => url_from_file_path(&path)?,
Cow::Borrowed(_) => url,
};

if method != Method::GET {
return Err(FetchError::FsNotGet(method));
}

let Options {
file_fetch_handler, ..
} = state.borrow_mut::<Options>();
Expand Down
3 changes: 2 additions & 1 deletion ext/fs/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::borrow::Cow;
use std::path::Path;
use std::path::PathBuf;

use deno_io::fs::FsError;
pub use deno_io::fs::FsError;
use deno_permissions::PermissionCheckError;

pub use crate::interface::AccessCheckCb;
Expand All @@ -23,6 +23,7 @@ pub use crate::ops::FsOpsError;
pub use crate::ops::FsOpsErrorKind;
pub use crate::ops::OperationError;
use crate::ops::*;
pub use crate::std_fs::open_with_access_check;
pub use crate::std_fs::RealFs;
pub use crate::sync::MaybeSend;
pub use crate::sync::MaybeSync;
Expand Down
2 changes: 1 addition & 1 deletion ext/fs/std_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,7 @@ fn open_options(options: OpenOptions) -> fs::OpenOptions {
}

#[inline(always)]
fn open_with_access_check(
pub fn open_with_access_check(
options: OpenOptions,
path: &Path,
access_check: Option<AccessCheckCb>,
Expand Down
22 changes: 13 additions & 9 deletions runtime/permissions/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2353,6 +2353,9 @@ pub enum PermissionCheckError {
#[class(uri)]
#[error(transparent)]
HostParse(#[from] HostParseError),
#[class("NotCapable")]
#[error("Permission denied {0}")]
NotCapable(&'static str),
}

/// Wrapper struct for `Permissions` that can be shared across threads.
Expand Down Expand Up @@ -2589,6 +2592,7 @@ impl PermissionsContainer {
}
.into_read();
inner.check(&desc, api_name)?;

Ok(Cow::Owned(desc.0.resolved))
}
}
Expand All @@ -2597,7 +2601,7 @@ impl PermissionsContainer {
/// by replacing it with the given `display`.
#[inline(always)]
pub fn check_read_blind(
&mut self,
&self,
path: &Path,
display: &str,
api_name: &str,
Expand Down Expand Up @@ -2714,7 +2718,7 @@ impl PermissionsContainer {

#[inline(always)]
pub fn check_write_partial(
&mut self,
&self,
path: &str,
api_name: &str,
) -> Result<PathBuf, PermissionCheckError> {
Expand All @@ -2731,7 +2735,7 @@ impl PermissionsContainer {

#[inline(always)]
pub fn check_run(
&mut self,
&self,
cmd: &RunQueryDescriptor,
api_name: &str,
) -> Result<(), PermissionCheckError> {
Expand Down Expand Up @@ -2767,25 +2771,25 @@ impl PermissionsContainer {
}

#[inline(always)]
pub fn check_env(&mut self, var: &str) -> Result<(), PermissionCheckError> {
pub fn check_env(&self, var: &str) -> Result<(), PermissionCheckError> {
self.inner.lock().env.check(var, None)?;
Ok(())
}

#[inline(always)]
pub fn check_env_all(&mut self) -> Result<(), PermissionCheckError> {
pub fn check_env_all(&self) -> Result<(), PermissionCheckError> {
self.inner.lock().env.check_all()?;
Ok(())
}

#[inline(always)]
pub fn check_sys_all(&mut self) -> Result<(), PermissionCheckError> {
pub fn check_sys_all(&self) -> Result<(), PermissionCheckError> {
self.inner.lock().sys.check_all()?;
Ok(())
}

#[inline(always)]
pub fn check_ffi_all(&mut self) -> Result<(), PermissionCheckError> {
pub fn check_ffi_all(&self) -> Result<(), PermissionCheckError> {
self.inner.lock().ffi.check_all()?;
Ok(())
}
Expand All @@ -2794,7 +2798,7 @@ impl PermissionsContainer {
/// permissions are enabled!
#[inline(always)]
pub fn check_was_allow_all_flag_passed(
&mut self,
&self,
) -> Result<(), PermissionCheckError> {
self.inner.lock().all.check()?;
Ok(())
Expand All @@ -2803,7 +2807,7 @@ impl PermissionsContainer {
/// Checks special file access, returning the failed permission type if
/// not successful.
pub fn check_special_file(
&mut self,
&self,
path: &Path,
_api_name: &str,
) -> Result<(), &'static str> {
Expand Down
3 changes: 2 additions & 1 deletion runtime/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ impl deno_fetch::FetchPermissions for Permissions {

fn check_read<'a>(
&mut self,
_resolved: bool,
_p: &'a Path,
_api_name: &str,
) -> Result<Cow<'a, Path>, PermissionCheckError> {
) -> Result<Cow<'a, Path>, FsError> {
unreachable!("snapshotting!")
}
}
Expand Down
Loading