0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-04 01:44:26 -05:00

perf(permissions): Fast exit from checks when permission is in "fully-granted" state (#22894)

Skips the access check if the specific unary permission is in an
all-granted state. Generally prevents an allocation or two.

Hooks up a quiet "all" permission that is automatically inherited. This
permission will be used in the future to indicate that the user wishes
to accept all side-effects of the permissions they explicitly granted.

The "all" permission is an "ambient flag"-style permission that states
whether "allow-all" was passed on the command-line.
This commit is contained in:
Matt Mastracci 2024-03-13 14:30:48 -06:00 committed by GitHub
parent eca7b0cddd
commit c9a9d040a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 310 additions and 299 deletions

View file

@ -867,7 +867,8 @@ mod tests {
fn create_test_worker() -> MainWorker { fn create_test_worker() -> MainWorker {
let main_module = let main_module =
resolve_path("./hello.js", &std::env::current_dir().unwrap()).unwrap(); resolve_path("./hello.js", &std::env::current_dir().unwrap()).unwrap();
let permissions = PermissionsContainer::new(Permissions::default()); let permissions =
PermissionsContainer::new(Permissions::none_without_prompt());
let options = WorkerOptions { let options = WorkerOptions {
startup_snapshot: crate::js::deno_isolate_init(), startup_snapshot: crate::js::deno_isolate_init(),

View file

@ -20,6 +20,7 @@ use once_cell::sync::Lazy;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt; use std::fmt;
use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -36,6 +37,16 @@ use prompter::PERMISSION_EMOJI;
pub use prompter::set_prompt_callbacks; pub use prompter::set_prompt_callbacks;
pub use prompter::PromptCallback; pub use prompter::PromptCallback;
/// Fast exit from permission check routines if this permission
/// is in the "fully-granted" state.
macro_rules! skip_check_if_is_permission_fully_granted {
($this:ident) => {
if $this.is_allow_all() {
return Ok(());
}
};
}
#[inline] #[inline]
fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> { fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> {
if path.is_absolute() { if path.is_absolute() {
@ -238,6 +249,31 @@ impl UnitPermission {
} }
result result
} }
fn create_child_permissions(
&mut self,
flag: ChildUnitPermissionArg,
) -> Result<Self, AnyError> {
let mut perm = self.clone();
match flag {
ChildUnitPermissionArg::Inherit => {
// copy
}
ChildUnitPermissionArg::Granted => {
if self.check().is_err() {
return Err(escalation_error());
}
perm.state = PermissionState::Granted;
}
ChildUnitPermissionArg::NotGranted => {
perm.state = PermissionState::Prompt;
}
}
if self.state == PermissionState::Denied {
perm.state = PermissionState::Denied;
}
Ok(perm)
}
} }
/// A normalized environment variable name. On Windows this will /// A normalized environment variable name. On Windows this will
@ -265,9 +301,20 @@ impl AsRef<str> for EnvVarName {
} }
} }
pub trait Descriptor: Eq + Clone { pub trait Descriptor: Eq + Clone + Hash {
type Arg; type Arg: From<String>;
/// Parse this descriptor from a list of Self::Arg, which may have been converted from
/// command-line strings.
fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError>; fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError>;
/// Generic check function to check this descriptor against a `UnaryPermission`.
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError>;
fn flag_name() -> &'static str; fn flag_name() -> &'static str;
fn name(&self) -> Cow<str>; fn name(&self) -> Cow<str>;
// By default, specifies no-stronger-than relationship. // By default, specifies no-stronger-than relationship.
@ -282,13 +329,13 @@ pub trait Descriptor: Eq + Clone {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct UnaryPermission<T: Descriptor + Hash> { pub struct UnaryPermission<T: Descriptor + Hash> {
pub granted_global: bool, granted_global: bool,
pub granted_list: HashSet<T>, granted_list: HashSet<T>,
pub flag_denied_global: bool, flag_denied_global: bool,
pub flag_denied_list: HashSet<T>, flag_denied_list: HashSet<T>,
pub prompt_denied_global: bool, prompt_denied_global: bool,
pub prompt_denied_list: HashSet<T>, prompt_denied_list: HashSet<T>,
pub prompt: bool, prompt: bool,
} }
impl<T: Descriptor + Hash> Default for UnaryPermission<T> { impl<T: Descriptor + Hash> Default for UnaryPermission<T> {
@ -313,6 +360,20 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
} }
} }
pub fn is_allow_all(&self) -> bool {
self.granted_global
&& self.flag_denied_list.is_empty()
&& self.prompt_denied_list.is_empty()
}
pub fn check_all_api(
&mut self,
api_name: Option<&str>,
) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, api_name, || None)
}
fn check_desc( fn check_desc(
&mut self, &mut self,
desc: &Option<T>, desc: &Option<T>,
@ -320,6 +381,7 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
api_name: Option<&str>, api_name: Option<&str>,
get_display_name: impl Fn() -> Option<String>, get_display_name: impl Fn() -> Option<String>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
let (result, prompted, is_allow_all) = self let (result, prompted, is_allow_all) = self
.query_desc(desc, AllowPartial::from(!assert_non_partial)) .query_desc(desc, AllowPartial::from(!assert_non_partial))
.check2( .check2(
@ -517,6 +579,45 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
None => *list_global = true, None => *list_global = true,
} }
} }
fn create_child_permissions(
&mut self,
flag: ChildUnaryPermissionArg,
) -> Result<UnaryPermission<T>, AnyError> {
let mut perms = Self::default();
match flag {
ChildUnaryPermissionArg::Inherit => {
perms = self.clone();
}
ChildUnaryPermissionArg::Granted => {
if self.check_all_api(None).is_err() {
return Err(escalation_error());
}
perms.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
let granted: Vec<T::Arg> =
granted_list.into_iter().map(From::from).collect();
perms.granted_list = T::parse(&Some(granted))?;
if !perms
.granted_list
.iter()
.all(|desc| desc.check_in_permission(self, None).is_ok())
{
return Err(escalation_error());
}
}
}
perms.flag_denied_global = self.flag_denied_global;
perms.flag_denied_list = self.flag_denied_list.clone();
perms.prompt_denied_global = self.prompt_denied_global;
perms.prompt_denied_list = self.prompt_denied_list.clone();
perms.prompt = self.prompt;
Ok(perms)
}
} }
#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Hash, Debug)]
@ -525,6 +626,14 @@ pub struct ReadDescriptor(pub PathBuf);
impl Descriptor for ReadDescriptor { impl Descriptor for ReadDescriptor {
type Arg = PathBuf; type Arg = PathBuf;
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError> {
UnaryPermission::<Self>::check(perm, &self.0, api_name)
}
fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> { fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
parse_path_list(args, ReadDescriptor) parse_path_list(args, ReadDescriptor)
} }
@ -548,6 +657,14 @@ pub struct WriteDescriptor(pub PathBuf);
impl Descriptor for WriteDescriptor { impl Descriptor for WriteDescriptor {
type Arg = PathBuf; type Arg = PathBuf;
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError> {
UnaryPermission::<Self>::check(perm, &self.0, api_name)
}
fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> { fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
parse_path_list(args, WriteDescriptor) parse_path_list(args, WriteDescriptor)
} }
@ -577,6 +694,14 @@ impl NetDescriptor {
impl Descriptor for NetDescriptor { impl Descriptor for NetDescriptor {
type Arg = String; type Arg = String;
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError> {
UnaryPermission::<Self>::check(perm, &(self.0.as_str(), self.1), api_name)
}
fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> { fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
parse_net_list(args) parse_net_list(args)
} }
@ -632,6 +757,14 @@ impl EnvDescriptor {
impl Descriptor for EnvDescriptor { impl Descriptor for EnvDescriptor {
type Arg = String; type Arg = String;
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError> {
UnaryPermission::<Self>::check(perm, &self.0.inner, api_name)
}
fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> { fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
parse_env_list(list) parse_env_list(list)
} }
@ -664,6 +797,14 @@ pub enum RunDescriptor {
impl Descriptor for RunDescriptor { impl Descriptor for RunDescriptor {
type Arg = String; type Arg = String;
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError> {
UnaryPermission::<Self>::check(perm, &self.to_string(), api_name)
}
fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> { fn parse(args: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
parse_run_list(args) parse_run_list(args)
} }
@ -723,12 +864,29 @@ impl ToString for RunDescriptor {
} }
} }
impl AsRef<Path> for RunDescriptor {
fn as_ref(&self) -> &Path {
match self {
RunDescriptor::Name(s) => s.as_ref(),
RunDescriptor::Path(s) => s.as_ref(),
}
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct SysDescriptor(pub String); pub struct SysDescriptor(pub String);
impl Descriptor for SysDescriptor { impl Descriptor for SysDescriptor {
type Arg = String; type Arg = String;
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError> {
UnaryPermission::<Self>::check(perm, &self.0, api_name)
}
fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> { fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
parse_sys_list(list) parse_sys_list(list)
} }
@ -756,6 +914,14 @@ pub struct FfiDescriptor(pub PathBuf);
impl Descriptor for FfiDescriptor { impl Descriptor for FfiDescriptor {
type Arg = PathBuf; type Arg = PathBuf;
fn check_in_permission(
&self,
perm: &mut UnaryPermission<Self>,
api_name: Option<&str>,
) -> Result<(), AnyError> {
UnaryPermission::<Self>::check(perm, &self.0, api_name)
}
fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> { fn parse(list: &Option<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
parse_path_list(list, FfiDescriptor) parse_path_list(list, FfiDescriptor)
} }
@ -798,6 +964,7 @@ impl UnaryPermission<ReadDescriptor> {
path: &Path, path: &Path,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc( self.check_desc(
&Some(ReadDescriptor(resolve_from_cwd(path)?)), &Some(ReadDescriptor(resolve_from_cwd(path)?)),
true, true,
@ -812,6 +979,7 @@ impl UnaryPermission<ReadDescriptor> {
path: &Path, path: &Path,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
let desc = ReadDescriptor(resolve_from_cwd(path)?); let desc = ReadDescriptor(resolve_from_cwd(path)?);
self.check_desc(&Some(desc), false, api_name, || { self.check_desc(&Some(desc), false, api_name, || {
Some(format!("\"{}\"", path.display())) Some(format!("\"{}\"", path.display()))
@ -826,6 +994,7 @@ impl UnaryPermission<ReadDescriptor> {
display: &str, display: &str,
api_name: &str, api_name: &str,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
let desc = ReadDescriptor(resolve_from_cwd(path)?); let desc = ReadDescriptor(resolve_from_cwd(path)?);
self.check_desc(&Some(desc), false, Some(api_name), || { self.check_desc(&Some(desc), false, Some(api_name), || {
Some(format!("<{display}>")) Some(format!("<{display}>"))
@ -833,6 +1002,7 @@ impl UnaryPermission<ReadDescriptor> {
} }
pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, api_name, || None) self.check_desc(&None, false, api_name, || None)
} }
} }
@ -862,6 +1032,7 @@ impl UnaryPermission<WriteDescriptor> {
path: &Path, path: &Path,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc( self.check_desc(
&Some(WriteDescriptor(resolve_from_cwd(path)?)), &Some(WriteDescriptor(resolve_from_cwd(path)?)),
true, true,
@ -876,6 +1047,7 @@ impl UnaryPermission<WriteDescriptor> {
path: &Path, path: &Path,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc( self.check_desc(
&Some(WriteDescriptor(resolve_from_cwd(path)?)), &Some(WriteDescriptor(resolve_from_cwd(path)?)),
false, false,
@ -892,6 +1064,7 @@ impl UnaryPermission<WriteDescriptor> {
display: &str, display: &str,
api_name: &str, api_name: &str,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
let desc = WriteDescriptor(resolve_from_cwd(path)?); let desc = WriteDescriptor(resolve_from_cwd(path)?);
self.check_desc(&Some(desc), false, Some(api_name), || { self.check_desc(&Some(desc), false, Some(api_name), || {
Some(format!("<{display}>")) Some(format!("<{display}>"))
@ -899,6 +1072,7 @@ impl UnaryPermission<WriteDescriptor> {
} }
pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, api_name, || None) self.check_desc(&None, false, api_name, || None)
} }
} }
@ -933,6 +1107,7 @@ impl UnaryPermission<NetDescriptor> {
host: &(T, Option<u16>), host: &(T, Option<u16>),
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&Some(NetDescriptor::new(&host)), false, api_name, || None) self.check_desc(&Some(NetDescriptor::new(&host)), false, api_name, || None)
} }
@ -941,6 +1116,7 @@ impl UnaryPermission<NetDescriptor> {
url: &url::Url, url: &url::Url,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
let hostname = url let hostname = url
.host_str() .host_str()
.ok_or_else(|| uri_error("Missing host"))? .ok_or_else(|| uri_error("Missing host"))?
@ -956,6 +1132,7 @@ impl UnaryPermission<NetDescriptor> {
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, None, || None) self.check_desc(&None, false, None, || None)
} }
} }
@ -976,11 +1153,17 @@ impl UnaryPermission<EnvDescriptor> {
self.revoke_desc(&env.map(EnvDescriptor::new)) self.revoke_desc(&env.map(EnvDescriptor::new))
} }
pub fn check(&mut self, env: &str) -> Result<(), AnyError> { pub fn check(
self.check_desc(&Some(EnvDescriptor::new(env)), false, None, || None) &mut self,
env: &str,
api_name: Option<&str>,
) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&Some(EnvDescriptor::new(env)), false, api_name, || None)
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, None, || None) self.check_desc(&None, false, None, || None)
} }
} }
@ -1006,6 +1189,7 @@ impl UnaryPermission<SysDescriptor> {
kind: &str, kind: &str,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc( self.check_desc(
&Some(SysDescriptor(kind.to_string())), &Some(SysDescriptor(kind.to_string())),
false, false,
@ -1015,6 +1199,7 @@ impl UnaryPermission<SysDescriptor> {
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, None, || None) self.check_desc(&None, false, None, || None)
} }
} }
@ -1042,6 +1227,7 @@ impl UnaryPermission<RunDescriptor> {
cmd: &str, cmd: &str,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc( self.check_desc(
&Some(RunDescriptor::from(cmd.to_string())), &Some(RunDescriptor::from(cmd.to_string())),
false, false,
@ -1051,6 +1237,7 @@ impl UnaryPermission<RunDescriptor> {
} }
pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, api_name, || None) self.check_desc(&None, false, api_name, || None)
} }
} }
@ -1079,6 +1266,7 @@ impl UnaryPermission<FfiDescriptor> {
path: &Path, path: &Path,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc( self.check_desc(
&Some(FfiDescriptor(resolve_from_cwd(path)?)), &Some(FfiDescriptor(resolve_from_cwd(path)?)),
true, true,
@ -1088,6 +1276,7 @@ impl UnaryPermission<FfiDescriptor> {
} }
pub fn check_partial(&mut self, path: Option<&Path>) -> Result<(), AnyError> { pub fn check_partial(&mut self, path: Option<&Path>) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
let desc = match path { let desc = match path {
Some(path) => Some(FfiDescriptor(resolve_from_cwd(path)?)), Some(path) => Some(FfiDescriptor(resolve_from_cwd(path)?)),
None => None, None => None,
@ -1098,6 +1287,7 @@ impl UnaryPermission<FfiDescriptor> {
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(&None, false, Some("all"), || None) self.check_desc(&None, false, Some("all"), || None)
} }
} }
@ -1111,24 +1301,10 @@ pub struct Permissions {
pub sys: UnaryPermission<SysDescriptor>, pub sys: UnaryPermission<SysDescriptor>,
pub run: UnaryPermission<RunDescriptor>, pub run: UnaryPermission<RunDescriptor>,
pub ffi: UnaryPermission<FfiDescriptor>, pub ffi: UnaryPermission<FfiDescriptor>,
pub all: UnitPermission,
pub hrtime: UnitPermission, pub hrtime: UnitPermission,
} }
impl Default for Permissions {
fn default() -> Self {
Self {
read: Permissions::new_unary(&None, &None, false).unwrap(),
write: Permissions::new_unary(&None, &None, false).unwrap(),
net: Permissions::new_unary(&None, &None, false).unwrap(),
env: Permissions::new_unary(&None, &None, false).unwrap(),
sys: Permissions::new_unary(&None, &None, false).unwrap(),
run: Permissions::new_unary(&None, &None, false).unwrap(),
ffi: Permissions::new_unary(&None, &None, false).unwrap(),
hrtime: Permissions::new_hrtime(false, false),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)]
pub struct PermissionsOptions { pub struct PermissionsOptions {
pub allow_all: bool, pub allow_all: bool,
@ -1183,6 +1359,16 @@ impl Permissions {
) )
} }
pub const fn new_all(allow_state: bool) -> UnitPermission {
unit_permission_from_flag_bools(
allow_state,
false,
"all",
"all",
false, // never prompt for all
)
}
pub fn from_options(opts: &PermissionsOptions) -> Result<Self, AnyError> { pub fn from_options(opts: &PermissionsOptions) -> Result<Self, AnyError> {
Ok(Self { Ok(Self {
read: Permissions::new_unary( read: Permissions::new_unary(
@ -1220,10 +1406,12 @@ impl Permissions {
&opts.deny_ffi, &opts.deny_ffi,
opts.prompt, opts.prompt,
)?, )?,
all: Permissions::new_all(opts.allow_all),
hrtime: Permissions::new_hrtime(opts.allow_hrtime, opts.deny_hrtime), hrtime: Permissions::new_hrtime(opts.allow_hrtime, opts.deny_hrtime),
}) })
} }
/// Create a set of permissions that explicitly allow everything.
pub fn allow_all() -> Self { pub fn allow_all() -> Self {
Self { Self {
read: UnaryPermission::allow_all(), read: UnaryPermission::allow_all(),
@ -1233,10 +1421,35 @@ impl Permissions {
sys: UnaryPermission::allow_all(), sys: UnaryPermission::allow_all(),
run: UnaryPermission::allow_all(), run: UnaryPermission::allow_all(),
ffi: UnaryPermission::allow_all(), ffi: UnaryPermission::allow_all(),
all: Permissions::new_all(true),
hrtime: Permissions::new_hrtime(true, false), hrtime: Permissions::new_hrtime(true, false),
} }
} }
/// Create a set of permissions that enable nothing, but will allow prompting.
pub fn none_with_prompt() -> Self {
Self::none(true)
}
/// Create a set of permissions that enable nothing, and will not allow prompting.
pub fn none_without_prompt() -> Self {
Self::none(false)
}
fn none(prompt: bool) -> Self {
Self {
read: Permissions::new_unary(&None, &None, prompt).unwrap(),
write: Permissions::new_unary(&None, &None, prompt).unwrap(),
net: Permissions::new_unary(&None, &None, prompt).unwrap(),
env: Permissions::new_unary(&None, &None, prompt).unwrap(),
sys: Permissions::new_unary(&None, &None, prompt).unwrap(),
run: Permissions::new_unary(&None, &None, prompt).unwrap(),
ffi: Permissions::new_unary(&None, &None, prompt).unwrap(),
all: Permissions::new_all(false),
hrtime: Permissions::new_hrtime(false, false),
}
}
/// A helper function that determines if the module specifier is a local or /// A helper function that determines if the module specifier is a local or
/// remote, and performs a read or net check for the specifier. /// remote, and performs a read or net check for the specifier.
pub fn check_specifier( pub fn check_specifier(
@ -1384,7 +1597,7 @@ impl PermissionsContainer {
#[inline(always)] #[inline(always)]
pub fn check_env(&mut self, var: &str) -> Result<(), AnyError> { pub fn check_env(&mut self, var: &str) -> Result<(), AnyError> {
self.0.lock().env.check(var) self.0.lock().env.check(var, None)
} }
#[inline(always)] #[inline(always)]
@ -1798,237 +2011,35 @@ pub fn create_child_permissions(
main_perms: &mut Permissions, main_perms: &mut Permissions,
child_permissions_arg: ChildPermissionsArg, child_permissions_arg: ChildPermissionsArg,
) -> Result<Permissions, AnyError> { ) -> Result<Permissions, AnyError> {
let mut worker_perms = Permissions::default(); let mut worker_perms = Permissions::none_without_prompt();
match child_permissions_arg.env { worker_perms.read = main_perms
ChildUnaryPermissionArg::Inherit => {
worker_perms.env = main_perms.env.clone();
}
ChildUnaryPermissionArg::Granted => {
if main_perms.env.check_all().is_err() {
return Err(escalation_error());
}
worker_perms.env.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
worker_perms.env.granted_list = parse_env_list(&Some(granted_list))?;
if !worker_perms
.env
.granted_list
.iter()
.all(|desc| main_perms.env.check(desc.as_ref()).is_ok())
{
return Err(escalation_error());
}
}
}
worker_perms.env.flag_denied_global = main_perms.env.flag_denied_global;
worker_perms.env.flag_denied_list = main_perms.env.flag_denied_list.clone();
worker_perms.env.prompt_denied_global = main_perms.env.prompt_denied_global;
worker_perms.env.prompt_denied_list =
main_perms.env.prompt_denied_list.clone();
worker_perms.env.prompt = main_perms.env.prompt;
match child_permissions_arg.sys {
ChildUnaryPermissionArg::Inherit => {
worker_perms.sys = main_perms.sys.clone();
}
ChildUnaryPermissionArg::Granted => {
if main_perms.sys.check_all().is_err() {
return Err(escalation_error());
}
worker_perms.sys.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
worker_perms.sys.granted_list = parse_sys_list(&Some(granted_list))?;
if !worker_perms
.sys
.granted_list
.iter()
.all(|desc| main_perms.sys.check(&desc.0, None).is_ok())
{
return Err(escalation_error());
}
}
}
worker_perms.sys.flag_denied_global = main_perms.sys.flag_denied_global;
worker_perms.sys.flag_denied_list = main_perms.sys.flag_denied_list.clone();
worker_perms.sys.prompt_denied_global = main_perms.sys.prompt_denied_global;
worker_perms.sys.prompt_denied_list =
main_perms.sys.prompt_denied_list.clone();
worker_perms.sys.prompt = main_perms.sys.prompt;
match child_permissions_arg.hrtime {
ChildUnitPermissionArg::Inherit => {
worker_perms.hrtime = main_perms.hrtime.clone();
}
ChildUnitPermissionArg::Granted => {
if main_perms.hrtime.check().is_err() {
return Err(escalation_error());
}
worker_perms.hrtime.state = PermissionState::Granted;
}
ChildUnitPermissionArg::NotGranted => {}
}
if main_perms.hrtime.state == PermissionState::Denied {
worker_perms.hrtime.state = PermissionState::Denied;
}
worker_perms.hrtime.prompt = main_perms.hrtime.prompt;
match child_permissions_arg.net {
ChildUnaryPermissionArg::Inherit => {
worker_perms.net = main_perms.net.clone();
}
ChildUnaryPermissionArg::Granted => {
if main_perms.net.check_all().is_err() {
return Err(escalation_error());
}
worker_perms.net.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
worker_perms.net.granted_list = parse_net_list(&Some(granted_list))?;
if !worker_perms
.net
.granted_list
.iter()
.all(|desc| main_perms.net.check(&(&desc.0, desc.1), None).is_ok())
{
return Err(escalation_error());
}
}
}
worker_perms.net.flag_denied_global = main_perms.net.flag_denied_global;
worker_perms.net.flag_denied_list = main_perms.net.flag_denied_list.clone();
worker_perms.net.prompt_denied_global = main_perms.net.prompt_denied_global;
worker_perms.net.prompt_denied_list =
main_perms.net.prompt_denied_list.clone();
worker_perms.net.prompt = main_perms.net.prompt;
match child_permissions_arg.ffi {
ChildUnaryPermissionArg::Inherit => {
worker_perms.ffi = main_perms.ffi.clone();
}
ChildUnaryPermissionArg::Granted => {
if main_perms.ffi.check_all().is_err() {
return Err(escalation_error());
}
worker_perms.ffi.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
worker_perms.ffi.granted_list = parse_path_list(
&Some(granted_list.iter().map(PathBuf::from).collect()),
FfiDescriptor,
)?;
if !worker_perms
.ffi
.granted_list
.iter()
.all(|desc| main_perms.ffi.check(&desc.0, None).is_ok())
{
return Err(escalation_error());
}
}
}
worker_perms.ffi.flag_denied_global = main_perms.env.flag_denied_global;
worker_perms.ffi.flag_denied_list = main_perms.ffi.flag_denied_list.clone();
worker_perms.ffi.prompt_denied_global = main_perms.ffi.prompt_denied_global;
worker_perms.ffi.prompt_denied_list =
main_perms.ffi.prompt_denied_list.clone();
worker_perms.ffi.prompt = main_perms.ffi.prompt;
match child_permissions_arg.read {
ChildUnaryPermissionArg::Inherit => {
worker_perms.read = main_perms.read.clone();
}
ChildUnaryPermissionArg::Granted => {
if main_perms.read.check_all(None).is_err() {
return Err(escalation_error());
}
worker_perms.read.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
worker_perms.read.granted_list = parse_path_list(
&Some(granted_list.iter().map(PathBuf::from).collect()),
ReadDescriptor,
)?;
if !worker_perms
.read .read
.granted_list .create_child_permissions(child_permissions_arg.read)?;
.iter() worker_perms.write = main_perms
.all(|desc| main_perms.read.check(&desc.0, None).is_ok())
{
return Err(escalation_error());
}
}
}
worker_perms.read.flag_denied_global = main_perms.read.flag_denied_global;
worker_perms.read.flag_denied_list = main_perms.read.flag_denied_list.clone();
worker_perms.read.prompt_denied_global = main_perms.read.prompt_denied_global;
worker_perms.read.prompt_denied_list =
main_perms.read.prompt_denied_list.clone();
worker_perms.read.prompt = main_perms.read.prompt;
match child_permissions_arg.run {
ChildUnaryPermissionArg::Inherit => {
worker_perms.run = main_perms.run.clone();
}
ChildUnaryPermissionArg::Granted => {
if main_perms.run.check_all(None).is_err() {
return Err(escalation_error());
}
worker_perms.run.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
worker_perms.run.granted_list = parse_run_list(&Some(granted_list))?;
if !worker_perms
.run
.granted_list
.iter()
.all(|desc| main_perms.run.check(&desc.to_string(), None).is_ok())
{
return Err(escalation_error());
}
}
}
worker_perms.run.flag_denied_global = main_perms.run.flag_denied_global;
worker_perms.run.flag_denied_list = main_perms.run.flag_denied_list.clone();
worker_perms.run.prompt_denied_global = main_perms.run.prompt_denied_global;
worker_perms.run.prompt_denied_list =
main_perms.run.prompt_denied_list.clone();
worker_perms.run.prompt = main_perms.run.prompt;
match child_permissions_arg.write {
ChildUnaryPermissionArg::Inherit => {
worker_perms.write = main_perms.write.clone();
}
ChildUnaryPermissionArg::Granted => {
if main_perms.write.check_all(None).is_err() {
return Err(escalation_error());
}
worker_perms.write.granted_global = true;
}
ChildUnaryPermissionArg::NotGranted => {}
ChildUnaryPermissionArg::GrantedList(granted_list) => {
worker_perms.write.granted_list = parse_path_list(
&Some(granted_list.iter().map(PathBuf::from).collect()),
WriteDescriptor,
)?;
if !worker_perms
.write .write
.granted_list .create_child_permissions(child_permissions_arg.write)?;
.iter() worker_perms.net = main_perms
.all(|desc| main_perms.write.check(&desc.0, None).is_ok()) .net
{ .create_child_permissions(child_permissions_arg.net)?;
return Err(escalation_error()); worker_perms.env = main_perms
} .env
} .create_child_permissions(child_permissions_arg.env)?;
} worker_perms.sys = main_perms
worker_perms.write.flag_denied_global = main_perms.write.flag_denied_global; .sys
worker_perms.write.flag_denied_list = .create_child_permissions(child_permissions_arg.sys)?;
main_perms.write.flag_denied_list.clone(); worker_perms.run = main_perms
worker_perms.write.prompt_denied_global = .run
main_perms.write.prompt_denied_global; .create_child_permissions(child_permissions_arg.run)?;
worker_perms.write.prompt_denied_list = worker_perms.ffi = main_perms
main_perms.write.prompt_denied_list.clone(); .ffi
worker_perms.write.prompt = main_perms.write.prompt; .create_child_permissions(child_permissions_arg.ffi)?;
worker_perms.hrtime = main_perms
.hrtime
.create_child_permissions(child_permissions_arg.hrtime)?;
worker_perms.all = main_perms
.all
.create_child_permissions(ChildUnitPermissionArg::Inherit)?;
Ok(worker_perms) Ok(worker_perms)
} }
@ -2436,6 +2447,7 @@ mod tests {
sys: Permissions::new_unary(&Some(svec!["hostname"]), &None, false) sys: Permissions::new_unary(&Some(svec!["hostname"]), &None, false)
.unwrap(), .unwrap(),
run: Permissions::new_unary(&Some(svec!["deno"]), &None, false).unwrap(), run: Permissions::new_unary(&Some(svec!["deno"]), &None, false).unwrap(),
all: Permissions::new_all(false),
hrtime: Permissions::new_hrtime(false, false), hrtime: Permissions::new_hrtime(false, false),
}; };
let perms3 = Permissions { let perms3 = Permissions {
@ -2463,6 +2475,7 @@ mod tests {
sys: Permissions::new_unary(&None, &Some(svec!["hostname"]), false) sys: Permissions::new_unary(&None, &Some(svec!["hostname"]), false)
.unwrap(), .unwrap(),
run: Permissions::new_unary(&None, &Some(svec!["deno"]), false).unwrap(), run: Permissions::new_unary(&None, &Some(svec!["deno"]), false).unwrap(),
all: Permissions::new_all(false),
hrtime: Permissions::new_hrtime(false, true), hrtime: Permissions::new_hrtime(false, true),
}; };
let perms4 = Permissions { let perms4 = Permissions {
@ -2500,6 +2513,7 @@ mod tests {
.unwrap(), .unwrap(),
run: Permissions::new_unary(&Some(vec![]), &Some(svec!["deno"]), false) run: Permissions::new_unary(&Some(vec![]), &Some(svec!["deno"]), false)
.unwrap(), .unwrap(),
all: Permissions::new_all(false),
hrtime: Permissions::new_hrtime(true, true), hrtime: Permissions::new_hrtime(true, true),
}; };
#[rustfmt::skip] #[rustfmt::skip]
@ -2586,7 +2600,7 @@ mod tests {
#[test] #[test]
fn test_request() { fn test_request() {
set_prompter(Box::new(TestPrompter)); set_prompter(Box::new(TestPrompter));
let mut perms: Permissions = Default::default(); let mut perms: Permissions = Permissions::none_without_prompt();
#[rustfmt::skip] #[rustfmt::skip]
{ {
let prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock(); let prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock();
@ -2663,6 +2677,7 @@ mod tests {
sys: Permissions::new_unary(&Some(svec!["hostname"]), &None, false) sys: Permissions::new_unary(&Some(svec!["hostname"]), &None, false)
.unwrap(), .unwrap(),
run: Permissions::new_unary(&Some(svec!["deno"]), &None, false).unwrap(), run: Permissions::new_unary(&Some(svec!["deno"]), &None, false).unwrap(),
all: Permissions::new_all(false),
hrtime: Permissions::new_hrtime(false, true), hrtime: Permissions::new_hrtime(false, true),
}; };
#[rustfmt::skip] #[rustfmt::skip]
@ -2689,17 +2704,7 @@ mod tests {
#[test] #[test]
fn test_check() { fn test_check() {
set_prompter(Box::new(TestPrompter)); set_prompter(Box::new(TestPrompter));
let mut perms = Permissions { let mut perms = Permissions::none_with_prompt();
read: Permissions::new_unary(&None, &None, true).unwrap(),
write: Permissions::new_unary(&None, &None, true).unwrap(),
net: Permissions::new_unary(&None, &None, true).unwrap(),
env: Permissions::new_unary(&None, &None, true).unwrap(),
sys: Permissions::new_unary(&None, &None, true).unwrap(),
run: Permissions::new_unary(&None, &None, true).unwrap(),
ffi: Permissions::new_unary(&None, &None, true).unwrap(),
hrtime: Permissions::new_hrtime(false, false),
};
let prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock(); let prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock();
prompt_value.set(true); prompt_value.set(true);
@ -2736,16 +2741,16 @@ mod tests {
assert!(perms.run.check("ls", None).is_err()); assert!(perms.run.check("ls", None).is_err());
prompt_value.set(true); prompt_value.set(true);
assert!(perms.env.check("HOME").is_ok()); assert!(perms.env.check("HOME", None).is_ok());
prompt_value.set(false); prompt_value.set(false);
assert!(perms.env.check("HOME").is_ok()); assert!(perms.env.check("HOME", None).is_ok());
assert!(perms.env.check("PATH").is_err()); assert!(perms.env.check("PATH", None).is_err());
prompt_value.set(true); prompt_value.set(true);
assert!(perms.env.check("hostname").is_ok()); assert!(perms.env.check("hostname", None).is_ok());
prompt_value.set(false); prompt_value.set(false);
assert!(perms.env.check("hostname").is_ok()); assert!(perms.env.check("hostname", None).is_ok());
assert!(perms.env.check("osRelease").is_err()); assert!(perms.env.check("osRelease", None).is_err());
assert!(perms.hrtime.check().is_err()); assert!(perms.hrtime.check().is_err());
} }
@ -2753,16 +2758,7 @@ mod tests {
#[test] #[test]
fn test_check_fail() { fn test_check_fail() {
set_prompter(Box::new(TestPrompter)); set_prompter(Box::new(TestPrompter));
let mut perms = Permissions { let mut perms = Permissions::none_with_prompt();
read: Permissions::new_unary(&None, &None, true).unwrap(),
write: Permissions::new_unary(&None, &None, true).unwrap(),
net: Permissions::new_unary(&None, &None, true).unwrap(),
env: Permissions::new_unary(&None, &None, true).unwrap(),
sys: Permissions::new_unary(&None, &None, true).unwrap(),
run: Permissions::new_unary(&None, &None, true).unwrap(),
ffi: Permissions::new_unary(&None, &None, true).unwrap(),
hrtime: Permissions::new_hrtime(false, false),
};
let prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock(); let prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock();
@ -2809,12 +2805,12 @@ mod tests {
assert!(perms.run.check("ls", None).is_ok()); assert!(perms.run.check("ls", None).is_ok());
prompt_value.set(false); prompt_value.set(false);
assert!(perms.env.check("HOME").is_err()); assert!(perms.env.check("HOME", None).is_err());
prompt_value.set(true); prompt_value.set(true);
assert!(perms.env.check("HOME").is_err()); assert!(perms.env.check("HOME", None).is_err());
assert!(perms.env.check("PATH").is_ok()); assert!(perms.env.check("PATH", None).is_ok());
prompt_value.set(false); prompt_value.set(false);
assert!(perms.env.check("PATH").is_ok()); assert!(perms.env.check("PATH", None).is_ok());
prompt_value.set(false); prompt_value.set(false);
assert!(perms.sys.check("hostname", None).is_err()); assert!(perms.sys.check("hostname", None).is_err());
@ -2842,10 +2838,10 @@ mod tests {
}; };
prompt_value.set(true); prompt_value.set(true);
assert!(perms.env.check("HOME").is_ok()); assert!(perms.env.check("HOME", None).is_ok());
prompt_value.set(false); prompt_value.set(false);
assert!(perms.env.check("HOME").is_ok()); assert!(perms.env.check("HOME", None).is_ok());
assert!(perms.env.check("hOmE").is_ok()); assert!(perms.env.check("hOmE", None).is_ok());
assert_eq!(perms.env.revoke(Some("HomE")), PermissionState::Prompt); assert_eq!(perms.env.revoke(Some("HomE")), PermissionState::Prompt);
} }
@ -2865,7 +2861,7 @@ mod tests {
false, false,
) )
.unwrap(), .unwrap(),
..Default::default() ..Permissions::none_without_prompt()
}; };
perms.read.check_partial(Path::new("/foo"), None).unwrap(); perms.read.check_partial(Path::new("/foo"), None).unwrap();
@ -3038,7 +3034,7 @@ mod tests {
hrtime: Permissions::new_hrtime(true, false), hrtime: Permissions::new_hrtime(true, false),
net: Permissions::new_unary(&Some(svec!["foo", "bar"]), &None, false) net: Permissions::new_unary(&Some(svec!["foo", "bar"]), &None, false)
.unwrap(), .unwrap(),
..Default::default() ..Permissions::none_without_prompt()
}; };
assert_eq!( assert_eq!(
create_child_permissions( create_child_permissions(
@ -3055,7 +3051,7 @@ mod tests {
Permissions { Permissions {
env: Permissions::new_unary(&Some(vec![]), &None, false).unwrap(), env: Permissions::new_unary(&Some(vec![]), &None, false).unwrap(),
net: Permissions::new_unary(&Some(svec!["foo"]), &None, false).unwrap(), net: Permissions::new_unary(&Some(svec!["foo"]), &None, false).unwrap(),
..Default::default() ..Permissions::none_without_prompt()
} }
); );
assert!(create_child_permissions( assert!(create_child_permissions(
@ -3104,6 +3100,13 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(main_perms, worker_perms); assert_eq!(main_perms, worker_perms);
assert_eq!(
main_perms.run.granted_list,
HashSet::from([
RunDescriptor::Name("bar".to_owned()),
RunDescriptor::Name("foo".to_owned())
])
);
} }
#[test] #[test]
@ -3131,14 +3134,21 @@ mod tests {
#[test] #[test]
fn test_handle_empty_value() { fn test_handle_empty_value() {
set_prompter(Box::new(TestPrompter)); set_prompter(Box::new(TestPrompter));
assert!(Permissions::new_unary::<ReadDescriptor>( assert!(Permissions::new_unary::<ReadDescriptor>(
&Some(vec![PathBuf::new()]), &Some(vec![Default::default()]),
&None, &None,
false false
) )
.is_err()); .is_err());
assert!(Permissions::new_unary::<EnvDescriptor>( assert!(Permissions::new_unary::<EnvDescriptor>(
&Some(vec![String::new()]), &Some(vec![Default::default()]),
&None,
false
)
.is_err());
assert!(Permissions::new_unary::<NetDescriptor>(
&Some(vec![Default::default()]),
&None, &None,
false false
) )