diff options
Diffstat (limited to 'runtime/ops')
-rw-r--r-- | runtime/ops/worker_host.rs | 376 |
1 files changed, 365 insertions, 11 deletions
diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index 871e4b9fe..da00c6e6e 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -1,14 +1,22 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::permissions::resolve_fs_allowlist; +use crate::permissions::PermissionState; use crate::permissions::Permissions; +use crate::permissions::UnaryPermission; use crate::web_worker::run_web_worker; use crate::web_worker::WebWorker; use crate::web_worker::WebWorkerHandle; use crate::web_worker::WorkerEvent; +use deno_core::error::custom_error; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::error::JsError; use deno_core::futures::channel::mpsc; +use deno_core::serde::de; +use deno_core::serde::de::SeqAccess; +use deno_core::serde::Deserialize; +use deno_core::serde::Deserializer; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; @@ -16,10 +24,12 @@ use deno_core::BufVec; use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_core::ZeroCopyBuf; -use serde::Deserialize; use std::cell::RefCell; use std::collections::HashMap; +use std::collections::HashSet; use std::convert::From; +use std::fmt; +use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; use std::thread::JoinHandle; @@ -27,6 +37,7 @@ use std::thread::JoinHandle; pub struct CreateWebWorkerArgs { pub name: String, pub worker_id: u32, + pub parent_permissions: Permissions, pub permissions: Permissions, pub main_module: ModuleSpecifier, pub use_deno_namespace: bool, @@ -47,6 +58,14 @@ struct HostUnhandledErrorArgs { message: String, } +pub struct WorkerThread { + join_handle: JoinHandle<Result<(), AnyError>>, + worker_handle: WebWorkerHandle, +} + +pub type WorkersTable = HashMap<u32, WorkerThread>; +pub type WorkerId = u32; + pub fn init( rt: &mut deno_core::JsRuntime, sender: Option<mpsc::Sender<WorkerEvent>>, @@ -86,21 +105,348 @@ pub fn init( ); } -pub struct WorkerThread { - join_handle: JoinHandle<Result<(), AnyError>>, - worker_handle: WebWorkerHandle, +fn merge_permission_state( + target: &PermissionState, + incoming: Option<PermissionState>, +) -> Result<PermissionState, AnyError> { + match target { + PermissionState::Granted => match incoming { + Some(x) => Ok(x), + None => Ok(*target), + }, + _ => match incoming { + Some(x) => match x { + PermissionState::Denied => Ok(x), + _ => Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )), + }, + None => Ok(*target), + }, + } } -pub type WorkersTable = HashMap<u32, WorkerThread>; -pub type WorkerId = u32; +fn check_net_permission_contains( + a: &HashSet<String>, + b: &HashSet<String>, +) -> bool { + b.iter().all(|x| a.contains(x)) +} + +fn merge_net_permissions( + target: &UnaryPermission<String>, + incoming: Option<UnaryPermission<String>>, +) -> Result<UnaryPermission<String>, AnyError> { + if incoming.is_none() { + return Ok(target.clone()); + }; + + let new_permissions = incoming.unwrap(); + match &target.global_state { + PermissionState::Granted => Ok(UnaryPermission::<String> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: new_permissions.denied_list, + }), + PermissionState::Prompt => match new_permissions.global_state { + //Throw + PermissionState::Granted => Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )), + //Merge + PermissionState::Prompt => { + if check_net_permission_contains( + &target.granted_list, + &new_permissions.granted_list, + ) { + Ok(UnaryPermission::<String> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: target.denied_list.clone(), + }) + } else { + Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )) + } + } + //Copy + PermissionState::Denied => Ok(UnaryPermission::<String> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: new_permissions.denied_list, + }), + }, + PermissionState::Denied => match new_permissions.global_state { + PermissionState::Denied => Ok(UnaryPermission::<String> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: new_permissions.denied_list, + }), + _ => Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )), + }, + } +} + +enum WorkerPermissionType { + READ, + WRITE, +} + +fn check_read_permissions( + allow_list: &HashSet<PathBuf>, + current_permissions: &Permissions, +) -> bool { + allow_list + .iter() + .all(|x| current_permissions.check_read(&x).is_ok()) +} + +fn check_write_permissions( + allow_list: &HashSet<PathBuf>, + current_permissions: &Permissions, +) -> bool { + allow_list + .iter() + .all(|x| current_permissions.check_write(&x).is_ok()) +} + +fn merge_read_write_permissions( + permission_type: WorkerPermissionType, + target: &UnaryPermission<PathBuf>, + incoming: Option<UnaryPermission<PathBuf>>, + current_permissions: &Permissions, +) -> Result<UnaryPermission<PathBuf>, AnyError> { + if incoming.is_none() { + return Ok(target.clone()); + }; + + let new_permissions = incoming.unwrap(); + match &target.global_state { + PermissionState::Granted => Ok(UnaryPermission::<PathBuf> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: new_permissions.denied_list, + }), + PermissionState::Prompt => match new_permissions.global_state { + //Throw + PermissionState::Granted => Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )), + //Merge + PermissionState::Prompt => { + if match permission_type { + WorkerPermissionType::READ => check_read_permissions( + &new_permissions.granted_list, + current_permissions, + ), + WorkerPermissionType::WRITE => check_write_permissions( + &new_permissions.granted_list, + current_permissions, + ), + } { + Ok(UnaryPermission::<PathBuf> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: target.denied_list.clone(), + }) + } else { + Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )) + } + } + //Copy + PermissionState::Denied => Ok(UnaryPermission::<PathBuf> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: new_permissions.denied_list, + }), + }, + PermissionState::Denied => match new_permissions.global_state { + PermissionState::Denied => Ok(UnaryPermission::<PathBuf> { + global_state: new_permissions.global_state, + granted_list: new_permissions.granted_list, + denied_list: new_permissions.denied_list, + }), + _ => Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )), + }, + } +} + +fn create_worker_permissions( + main_thread_permissions: &Permissions, + permission_args: PermissionsArg, +) -> Result<Permissions, AnyError> { + Ok(Permissions { + env: merge_permission_state( + &main_thread_permissions.env, + permission_args.env, + )?, + hrtime: merge_permission_state( + &main_thread_permissions.hrtime, + permission_args.hrtime, + )?, + net: merge_net_permissions( + &main_thread_permissions.net, + permission_args.net, + )?, + plugin: merge_permission_state( + &main_thread_permissions.plugin, + permission_args.plugin, + )?, + read: merge_read_write_permissions( + WorkerPermissionType::READ, + &main_thread_permissions.read, + permission_args.read, + &main_thread_permissions, + )?, + run: merge_permission_state( + &main_thread_permissions.run, + permission_args.run, + )?, + write: merge_read_write_permissions( + WorkerPermissionType::WRITE, + &main_thread_permissions.write, + permission_args.write, + &main_thread_permissions, + )?, + }) +} + +#[derive(Debug, Deserialize)] +struct PermissionsArg { + #[serde(default, deserialize_with = "as_permission_state")] + env: Option<PermissionState>, + #[serde(default, deserialize_with = "as_permission_state")] + hrtime: Option<PermissionState>, + #[serde(default, deserialize_with = "as_unary_string_permission")] + net: Option<UnaryPermission<String>>, + #[serde(default, deserialize_with = "as_permission_state")] + plugin: Option<PermissionState>, + #[serde(default, deserialize_with = "as_unary_path_permission")] + read: Option<UnaryPermission<PathBuf>>, + #[serde(default, deserialize_with = "as_permission_state")] + run: Option<PermissionState>, + #[serde(default, deserialize_with = "as_unary_path_permission")] + write: Option<UnaryPermission<PathBuf>>, +} + +fn as_permission_state<'de, D>( + deserializer: D, +) -> Result<Option<PermissionState>, D::Error> +where + D: Deserializer<'de>, +{ + let value: bool = Deserialize::deserialize(deserializer)?; + + match value { + true => Ok(Some(PermissionState::Granted)), + false => Ok(Some(PermissionState::Denied)), + } +} + +struct UnaryPermissionBase { + global_state: PermissionState, + paths: Vec<String>, +} + +struct ParseBooleanOrStringVec; + +impl<'de> de::Visitor<'de> for ParseBooleanOrStringVec { + type Value = UnaryPermissionBase; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a vector of strings or a boolean") + } + + fn visit_bool<E>(self, v: bool) -> Result<UnaryPermissionBase, E> + where + E: de::Error, + { + Ok(UnaryPermissionBase { + global_state: match v { + true => PermissionState::Granted, + false => PermissionState::Denied, + }, + paths: Vec::new(), + }) + } + + fn visit_seq<V>(self, mut visitor: V) -> Result<UnaryPermissionBase, V::Error> + where + V: SeqAccess<'de>, + { + let mut vec: Vec<String> = Vec::new(); + + let mut value = visitor.next_element::<String>()?; + while value.is_some() { + vec.push(value.unwrap()); + value = visitor.next_element()?; + } + Ok(UnaryPermissionBase { + global_state: PermissionState::Prompt, + paths: vec, + }) + } +} + +fn as_unary_string_permission<'de, D>( + deserializer: D, +) -> Result<Option<UnaryPermission<String>>, D::Error> +where + D: Deserializer<'de>, +{ + let value: UnaryPermissionBase = + deserializer.deserialize_any(ParseBooleanOrStringVec)?; + + let allowed: HashSet<String> = value.paths.into_iter().collect(); + + Ok(Some(UnaryPermission::<String> { + global_state: value.global_state, + granted_list: allowed, + ..Default::default() + })) +} + +fn as_unary_path_permission<'de, D>( + deserializer: D, +) -> Result<Option<UnaryPermission<PathBuf>>, D::Error> +where + D: Deserializer<'de>, +{ + let value: UnaryPermissionBase = + deserializer.deserialize_any(ParseBooleanOrStringVec)?; + + let paths: Vec<PathBuf> = + value.paths.into_iter().map(PathBuf::from).collect(); + + Ok(Some(UnaryPermission::<PathBuf> { + global_state: value.global_state, + granted_list: resolve_fs_allowlist(&Some(paths)), + ..Default::default() + })) +} #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct CreateWorkerArgs { - name: Option<String>, - specifier: String, has_source_code: bool, + name: Option<String>, + permissions: Option<PermissionsArg>, source_code: String, + specifier: String, use_deno_namespace: bool, } @@ -121,9 +467,16 @@ fn op_create_worker( let args_name = args.name; let use_deno_namespace = args.use_deno_namespace; if use_deno_namespace { - super::check_unstable(state, "Worker.deno"); + super::check_unstable(state, "Worker.deno.namespace"); } - let permissions = state.borrow::<Permissions>().clone(); + let parent_permissions = state.borrow::<Permissions>().clone(); + let worker_permissions = if let Some(permissions) = args.permissions { + super::check_unstable(state, "Worker.deno.permissions"); + create_worker_permissions(&parent_permissions, permissions)? + } else { + parent_permissions.clone() + }; + let worker_id = state.take::<WorkerId>(); let create_module_loader = state.take::<CreateWebWorkerCbHolder>(); state.put::<CreateWebWorkerCbHolder>(create_module_loader.clone()); @@ -149,7 +502,8 @@ fn op_create_worker( let worker = (create_module_loader.0)(CreateWebWorkerArgs { name: worker_name, worker_id, - permissions, + parent_permissions, + permissions: worker_permissions, main_module: module_specifier.clone(), use_deno_namespace, }); |