fix(runtime/ops/worker_host): move permission arg parsing to Rust (#12297)

This commit is contained in:
Nayeem Rahman 2021-10-13 18:04:44 +01:00 committed by GitHub
parent 43a63530ac
commit 7a22df9b76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1201 additions and 823 deletions

View file

@ -2178,6 +2178,7 @@ declare namespace Deno {
export interface FfiPermissionDescriptor {
name: "ffi";
path?: string | URL;
}
export interface HrtimePermissionDescriptor {

View file

@ -910,10 +910,12 @@ declare namespace Deno {
* If set to `"inherit"`, the current `ffi` permission will be inherited.
* If set to `true`, the global `ffi` permission will be requested.
* If set to `false`, the global `ffi` permission will be revoked.
* If set to `Array<string | URL>`, the `ffi` permission will be requested with the
* specified file paths.
*
* Defaults to "inherit".
*/
ffi?: "inherit" | boolean;
ffi?: "inherit" | boolean | Array<string | URL>;
/** Specifies if the `read` permission should be requested or revoked.
* If set to `"inherit"`, the current `read` permission will be inherited.
@ -1237,7 +1239,7 @@ declare interface WorkerOptions {
* For example: `["https://deno.land", "localhost:8080"]`.
*/
net?: "inherit" | boolean | string[];
ffi?: "inherit" | boolean;
ffi?: "inherit" | boolean | Array<string | URL>;
read?: "inherit" | boolean | Array<string | URL>;
run?: "inherit" | boolean | Array<string | URL>;
write?: "inherit" | boolean | Array<string | URL>;

View file

@ -198,7 +198,7 @@ pub struct Flags {
pub allow_env: Option<Vec<String>>,
pub allow_hrtime: bool,
pub allow_net: Option<Vec<String>>,
pub allow_ffi: Option<Vec<String>>,
pub allow_ffi: Option<Vec<PathBuf>>,
pub allow_read: Option<Vec<PathBuf>>,
pub allow_run: Option<Vec<String>>,
pub allow_write: Option<Vec<PathBuf>>,
@ -324,7 +324,7 @@ impl Flags {
args.push("--allow-ffi".to_string());
}
Some(ffi_allowlist) => {
let s = format!("--allow-ffi={}", ffi_allowlist.join(","));
let s = format!("--allow-ffi={}", join_paths(ffi_allowlist, ","));
args.push(s);
}
_ => {}
@ -1685,10 +1685,10 @@ fn config_arg<'a, 'b>() -> Arg<'a, 'b> {
.long_help(
"Load configuration file.
Before 1.14 Deno only supported loading tsconfig.json that allowed
to customise TypeScript compiler settings.
to customise TypeScript compiler settings.
Starting with 1.14 configuration file can be used to configure different
subcommands like `deno lint` or `deno fmt`.
Starting with 1.14 configuration file can be used to configure different
subcommands like `deno lint` or `deno fmt`.
It's recommended to use `deno.json` or `deno.jsonc` as a filename.",
)
@ -2202,7 +2202,7 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
}
if let Some(ffi_wl) = matches.values_of("allow-ffi") {
let ffi_allowlist: Vec<String> = ffi_wl.map(ToString::to_string).collect();
let ffi_allowlist: Vec<PathBuf> = ffi_wl.map(PathBuf::from).collect();
flags.allow_ffi = Some(ffi_allowlist);
debug!("ffi allowlist: {:#?}", &flags.allow_ffi);
}

View file

@ -4,8 +4,8 @@ use deno_core::error::AnyError;
use deno_core::JsRuntime;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_runtime::ops::worker_host::create_worker_permissions;
use deno_runtime::ops::worker_host::PermissionsArg;
use deno_runtime::permissions::create_child_permissions;
use deno_runtime::permissions::ChildPermissionsArg;
use deno_runtime::permissions::Permissions;
use std::sync::mpsc::Sender;
use uuid::Uuid;
@ -26,15 +26,15 @@ struct PermissionsHolder(Uuid, Permissions);
pub fn op_pledge_test_permissions(
state: &mut OpState,
args: PermissionsArg,
args: ChildPermissionsArg,
_: (),
) -> Result<Uuid, AnyError> {
deno_runtime::ops::check_unstable(state, "Deno.test.permissions");
let token = Uuid::new_v4();
let parent_permissions = state.borrow::<Permissions>().clone();
let worker_permissions =
create_worker_permissions(parent_permissions.clone(), args)?;
let parent_permissions = state.borrow_mut::<Permissions>();
let worker_permissions = create_child_permissions(parent_permissions, args)?;
let parent_permissions = parent_permissions.clone();
state.put::<PermissionsHolder>(PermissionsHolder(token, parent_permissions));

View file

@ -3,7 +3,7 @@
use crate::itest;
itest!(workers {
args: "test --reload --location http://127.0.0.1:4545/ --allow-net --allow-read --unstable workers/test.ts",
args: "test --reload --location http://127.0.0.1:4545/ -A --unstable workers/test.ts",
output: "workers/test.ts.out",
http_server: true,
});

View file

@ -18,7 +18,7 @@ for (const name of permissions) {
},
async fn() {
const status = await Deno.permissions.query({ name });
assertEquals(status.state, "denied");
assertEquals(status.state, "prompt");
},
});

View file

@ -6,12 +6,12 @@ self.onmessage = async () => {
const run = await Deno.permissions.query({ name: "run" });
const write = await Deno.permissions.query({ name: "write" });
self.postMessage(
hrtime.state === "denied" &&
net.state === "denied" &&
ffi.state === "denied" &&
read.state === "denied" &&
run.state === "denied" &&
write.state === "denied",
hrtime.state === "prompt" &&
net.state === "prompt" &&
ffi.state === "prompt" &&
read.state === "prompt" &&
run.state === "prompt" &&
write.state === "prompt",
);
self.close();
};

View file

@ -1,41 +0,0 @@
const worker = new Worker(
new URL("./read_check_granular_worker.js", import.meta.url).href,
{
type: "module",
deno: {
namespace: true,
permissions: {
read: [],
},
},
},
);
let received = 0;
const messages = [];
worker.onmessage = ({ data: childResponse }) => {
received++;
postMessage({
childHasPermission: childResponse.hasPermission,
index: childResponse.index,
parentHasPermission: messages[childResponse.index],
});
if (received === messages.length) {
worker.terminate();
}
};
onmessage = async ({ data }) => {
const { state } = await Deno.permissions.query({
name: "read",
path: data.path,
});
messages[data.index] = state === "granted";
worker.postMessage({
index: data.index,
route: data.route,
});
};

View file

@ -1,27 +1,18 @@
onmessage = async () => {
const { state } = await Deno.permissions.query({
name: "read",
});
const worker = new Worker(
new URL("./read_check_worker.js", import.meta.url).href,
{
type: "module",
deno: {
namespace: true,
permissions: {
read: false,
},
},
const worker = new Worker(
new URL("./read_check_granular_worker.js", import.meta.url).href,
{
type: "module",
deno: {
namespace: true,
permissions: "none",
},
);
},
);
worker.onmessage = ({ data: childHasPermission }) => {
postMessage({
parentHasPermission: state === "granted",
childHasPermission,
});
close();
};
worker.postMessage(null);
onmessage = ({ data }) => {
worker.postMessage(data);
};
worker.onmessage = ({ data }) => {
postMessage(data);
};

View file

@ -1,11 +1,29 @@
onmessage = async ({ data }) => {
const { state } = await Deno.permissions.query({
name: "read",
path: data.path,
});
postMessage({
hasPermission: state === "granted",
index: data.index,
});
};
// deno-fmt-ignore-file
postMessage({
envGlobal: (await Deno.permissions.query({ name: "env" })).state,
envFoo: (await Deno.permissions.query({ name: "env", variable: "foo" })).state,
envAbsent: (await Deno.permissions.query({ name: "env", variable: "absent" })).state,
hrtime: (await Deno.permissions.query({ name: "hrtime" })).state,
netGlobal: (await Deno.permissions.query({ name: "net" })).state,
netFoo: (await Deno.permissions.query({ name: "net", host: "foo" })).state,
netFoo8000: (await Deno.permissions.query({ name: "net", host: "foo:8000" })).state,
netBar: (await Deno.permissions.query({ name: "net", host: "bar" })).state,
netBar8000: (await Deno.permissions.query({ name: "net", host: "bar:8000" })).state,
ffiGlobal: (await Deno.permissions.query({ name: "ffi" })).state,
ffiFoo: (await Deno.permissions.query({ name: "ffi", path: new URL("foo", import.meta.url) })).state,
ffiBar: (await Deno.permissions.query({ name: "ffi", path: "bar" })).state,
ffiAbsent: (await Deno.permissions.query({ name: "ffi", path: "absent" })).state,
readGlobal: (await Deno.permissions.query({ name: "read" })).state,
readFoo: (await Deno.permissions.query({ name: "read", path: new URL("foo", import.meta.url) })).state,
readBar: (await Deno.permissions.query({ name: "read", path: "bar" })).state,
readAbsent: (await Deno.permissions.query({ name: "read", path: "absent" })).state,
runGlobal: (await Deno.permissions.query({ name: "run" })).state,
runFoo: (await Deno.permissions.query({ name: "run", command: new URL("foo", import.meta.url) })).state,
runBar: (await Deno.permissions.query({ name: "run", command: "bar" })).state,
runBaz: (await Deno.permissions.query({ name: "run", command: "./baz" })).state,
runAbsent: (await Deno.permissions.query({ name: "run", command: "absent" })).state,
writeGlobal: (await Deno.permissions.query({ name: "write" })).state,
writeFoo: (await Deno.permissions.query({ name: "write", path: new URL("foo", import.meta.url) })).state,
writeBar: (await Deno.permissions.query({ name: "write", path: "bar" })).state,
writeAbsent: (await Deno.permissions.query({ name: "write", path: "absent" })).state,
});

View file

@ -8,7 +8,6 @@ import {
assertThrows,
} from "../../../../test_util/std/testing/asserts.ts";
import { deferred } from "../../../../test_util/std/async/deferred.ts";
import { fromFileUrl } from "../../../../test_util/std/path/mod.ts";
Deno.test({
name: "worker terminate",
@ -454,7 +453,6 @@ Deno.test("Worker limit children permissions", async function () {
});
Deno.test("Worker limit children permissions granularly", async function () {
const promise = deferred();
const worker = new Worker(
new URL("./read_check_granular_worker.js", import.meta.url).href,
{
@ -462,53 +460,52 @@ Deno.test("Worker limit children permissions granularly", async function () {
deno: {
namespace: true,
permissions: {
read: [
new URL("./read_check_worker.js", import.meta.url),
],
env: ["foo"],
hrtime: true,
net: ["foo", "bar:8000"],
ffi: [new URL("foo", import.meta.url), "bar"],
read: [new URL("foo", import.meta.url), "bar"],
run: [new URL("foo", import.meta.url), "bar", "./baz"],
write: [new URL("foo", import.meta.url), "bar"],
},
},
},
);
//Routes are relative to the spawned worker location
const routes = [
{
permission: false,
path: fromFileUrl(
new URL("read_check_granular_worker.js", import.meta.url),
),
},
{
permission: true,
path: fromFileUrl(new URL("read_check_worker.js", import.meta.url)),
},
];
let checked = 0;
worker.onmessage = ({ data }) => {
checked++;
assertEquals(data.hasPermission, routes[data.index].permission);
routes.shift();
if (checked === routes.length) {
promise.resolve();
}
};
routes.forEach(({ path }, index) =>
worker.postMessage({
index,
path,
})
);
await promise;
const promise = deferred();
worker.onmessage = ({ data }) => promise.resolve(data);
assertEquals(await promise, {
envGlobal: "prompt",
envFoo: "granted",
envAbsent: "prompt",
hrtime: "granted",
netGlobal: "prompt",
netFoo: "granted",
netFoo8000: "granted",
netBar: "prompt",
netBar8000: "granted",
ffiGlobal: "prompt",
ffiFoo: "granted",
ffiBar: "granted",
ffiAbsent: "prompt",
readGlobal: "prompt",
readFoo: "granted",
readBar: "granted",
readAbsent: "prompt",
runGlobal: "prompt",
runFoo: "granted",
runBar: "granted",
runBaz: "granted",
runAbsent: "prompt",
writeGlobal: "prompt",
writeFoo: "granted",
writeBar: "granted",
writeAbsent: "prompt",
});
worker.terminate();
});
Deno.test("Nested worker limit children permissions", async function () {
const promise = deferred();
/** This worker has read permissions but doesn't grant them to its children */
/** This worker has permissions but doesn't grant them to its children */
const worker = new Worker(
new URL("./parent_read_check_worker.js", import.meta.url).href,
{
@ -519,104 +516,65 @@ Deno.test("Nested worker limit children permissions", async function () {
},
},
);
worker.onmessage = ({ data }) => {
assert(data.parentHasPermission);
assert(!data.childHasPermission);
promise.resolve();
};
worker.postMessage(null);
await promise;
worker.terminate();
});
Deno.test("Nested worker limit children permissions granularly", async function () {
const promise = deferred();
/** This worker has read permissions but doesn't grant them to its children */
const worker = new Worker(
new URL("./parent_read_check_granular_worker.js", import.meta.url)
.href,
{
type: "module",
deno: {
namespace: true,
permissions: {
read: [
new URL("./read_check_granular_worker.js", import.meta.url),
],
},
},
},
);
//Routes are relative to the spawned worker location
const routes = [
{
childHasPermission: false,
parentHasPermission: true,
path: fromFileUrl(
new URL("read_check_granular_worker.js", import.meta.url),
),
},
{
childHasPermission: false,
parentHasPermission: false,
path: fromFileUrl(new URL("read_check_worker.js", import.meta.url)),
},
];
let checked = 0;
worker.onmessage = ({ data }) => {
checked++;
assertEquals(
data.childHasPermission,
routes[data.index].childHasPermission,
);
assertEquals(
data.parentHasPermission,
routes[data.index].parentHasPermission,
);
if (checked === routes.length) {
promise.resolve();
}
};
// Index needed cause requests will be handled asynchronously
routes.forEach(({ path }, index) =>
worker.postMessage({
index,
path,
})
);
await promise;
worker.onmessage = ({ data }) => promise.resolve(data);
assertEquals(await promise, {
envGlobal: "prompt",
envFoo: "prompt",
envAbsent: "prompt",
hrtime: "prompt",
netGlobal: "prompt",
netFoo: "prompt",
netFoo8000: "prompt",
netBar: "prompt",
netBar8000: "prompt",
ffiGlobal: "prompt",
ffiFoo: "prompt",
ffiBar: "prompt",
ffiAbsent: "prompt",
readGlobal: "prompt",
readFoo: "prompt",
readBar: "prompt",
readAbsent: "prompt",
runGlobal: "prompt",
runFoo: "prompt",
runBar: "prompt",
runBaz: "prompt",
runAbsent: "prompt",
writeGlobal: "prompt",
writeFoo: "prompt",
writeBar: "prompt",
writeAbsent: "prompt",
});
worker.terminate();
});
// This test relies on env permissions not being granted on main thread
Deno.test("Worker initialization throws on worker permissions greater than parent thread permissions", function () {
assertThrows(
() => {
const worker = new Worker(
new URL("./deno_worker.ts", import.meta.url).href,
{
type: "module",
deno: {
namespace: true,
permissions: {
env: true,
Deno.test({
name:
"Worker initialization throws on worker permissions greater than parent thread permissions",
permissions: { env: false },
fn: function () {
assertThrows(
() => {
const worker = new Worker(
new URL("./deno_worker.ts", import.meta.url).href,
{
type: "module",
deno: {
namespace: true,
permissions: {
env: true,
},
},
},
},
);
worker.terminate();
},
Deno.errors.PermissionDenied,
"Can't escalate parent thread permissions",
);
);
worker.terminate();
},
Deno.errors.PermissionDenied,
"Can't escalate parent thread permissions",
);
},
});
Deno.test("Worker with disabled permissions", async function () {
@ -643,6 +601,19 @@ Deno.test("Worker with disabled permissions", async function () {
worker.terminate();
});
Deno.test("Worker with invalid permission arg", function () {
assertThrows(
() =>
new Worker(`data:,close();`, {
type: "module",
// @ts-expect-error invalid env value
deno: { permissions: { env: "foo" } },
}),
TypeError,
'Error parsing args: (deno.permissions.env) invalid value: string "foo", expected "inherit" or boolean or string[]',
);
});
Deno.test({
name: "worker location",
fn: async function () {

View file

@ -20,6 +20,8 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ffi::c_void;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
pub struct Unstable(pub bool);
@ -37,7 +39,7 @@ fn check_unstable(state: &OpState, api_name: &str) {
}
pub trait FfiPermissions {
fn check(&mut self, path: &str) -> Result<(), AnyError>;
fn check(&mut self, path: &Path) -> Result<(), AnyError>;
}
#[derive(Clone)]
@ -366,7 +368,7 @@ where
check_unstable(state, "Deno.dlopen");
let permissions = state.borrow_mut::<FP>();
permissions.check(&path)?;
permissions.check(&PathBuf::from(&path))?;
let lib = Library::open(&path).map_err(|e| {
dlopen::Error::OpeningLibraryError(std::io::Error::new(

View file

@ -83,7 +83,10 @@ mod not_docs {
}
impl deno_ffi::FfiPermissions for Permissions {
fn check(&mut self, _path: &str) -> Result<(), deno_core::error::AnyError> {
fn check(
&mut self,
_path: &Path,
) -> Result<(), deno_core::error::AnyError> {
unreachable!("snapshotting!")
}
}

View file

@ -10,7 +10,10 @@
} = window;
const { pathFromURL } = window.__bootstrap.util;
const {
ArrayIsArray,
ArrayPrototypeIncludes,
ArrayPrototypeMap,
ArrayPrototypeSlice,
Map,
MapPrototypeGet,
MapPrototypeHas,
@ -162,7 +165,9 @@
);
}
if (desc.name === "read" || desc.name === "write") {
if (
desc.name === "read" || desc.name === "write" || desc.name === "ffi"
) {
desc.path = pathFromURL(desc.path);
} else if (desc.name === "run") {
desc.command = pathFromURL(desc.command);
@ -213,7 +218,34 @@
const permissions = new Permissions(illegalConstructorKey);
/** Converts all file URLs in FS allowlists to paths. */
function serializePermissions(permissions) {
if (typeof permissions == "object" && permissions != null) {
const serializedPermissions = {};
for (const key of ["read", "write", "run", "ffi"]) {
if (ArrayIsArray(permissions[key])) {
serializedPermissions[key] = ArrayPrototypeMap(
permissions[key],
(path) => pathFromURL(path),
);
} else {
serializedPermissions[key] = permissions[key];
}
}
for (const key of ["env", "hrtime", "net"]) {
if (ArrayIsArray(permissions[key])) {
serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]);
} else {
serializedPermissions[key] = permissions[key];
}
}
return serializedPermissions;
}
return permissions;
}
window.__bootstrap.permissions = {
serializePermissions,
permissions,
Permissions,
PermissionStatus,

View file

@ -4,8 +4,6 @@
((window) => {
const core = window.Deno.core;
const {
ArrayIsArray,
ArrayPrototypeMap,
Error,
StringPrototypeStartsWith,
String,
@ -15,7 +13,8 @@
const webidl = window.__bootstrap.webidl;
const { URL } = window.__bootstrap.url;
const { getLocationHref } = window.__bootstrap.location;
const { log, pathFromURL } = window.__bootstrap.util;
const { serializePermissions } = window.__bootstrap.permissions;
const { log } = window.__bootstrap.util;
const { defineEventHandler } = window.__bootstrap.event;
const { deserializeJsMessageData, serializeJsMessageData } =
window.__bootstrap.messagePort;
@ -32,7 +31,7 @@
return core.opSync("op_create_worker", {
hasSourceCode,
name,
permissions,
permissions: serializePermissions(permissions),
sourceCode,
specifier,
useDenoNamespace,
@ -56,87 +55,6 @@
return core.opAsync("op_host_recv_message", id);
}
/**
* @param {"inherit" | boolean} value
* @param {string} permission
* @return {boolean}
*/
function parseUnitPermission(
value,
permission,
) {
if (value !== "inherit" && typeof value !== "boolean") {
throw new Error(
`Expected 'boolean' for ${permission} permission, ${typeof value} received`,
);
}
return value === "inherit" ? undefined : value;
}
/**
* @param {string} permission
* @return {(boolean | string[])}
*/
function parseArrayPermission(
value,
permission,
) {
if (typeof value === "string") {
if (value !== "inherit") {
throw new Error(
`Expected 'array' or 'boolean' for ${permission} permission, "${value}" received`,
);
}
} else if (!ArrayIsArray(value) && typeof value !== "boolean") {
throw new Error(
`Expected 'array' or 'boolean' for ${permission} permission, ${typeof value} received`,
);
//Casts URLs to absolute routes
} else if (ArrayIsArray(value)) {
value = ArrayPrototypeMap(value, (route) => {
if (route instanceof URL) {
if (permission === "net") {
throw new Error(
`Expected 'string' for net permission, received 'URL'`,
);
} else if (permission === "env") {
throw new Error(
`Expected 'string' for env permission, received 'URL'`,
);
} else {
route = pathFromURL(route);
}
}
return route;
});
}
return value === "inherit" ? undefined : value;
}
/**
* Normalizes data, runs checks on parameters and deletes inherited permissions
*/
function parsePermissions({
env = "inherit",
hrtime = "inherit",
net = "inherit",
ffi = "inherit",
read = "inherit",
run = "inherit",
write = "inherit",
}) {
return {
env: parseArrayPermission(env, "env"),
hrtime: parseUnitPermission(hrtime, "hrtime"),
net: parseArrayPermission(net, "net"),
ffi: parseUnitPermission(ffi, "ffi"),
read: parseArrayPermission(read, "read"),
run: parseUnitPermission(run, "run"),
write: parseArrayPermission(write, "write"),
};
}
class Worker extends EventTarget {
#id = 0;
#name = "";
@ -152,43 +70,23 @@
super();
specifier = String(specifier);
const {
deno = {},
name = "unknown",
deno,
name,
type = "classic",
} = options;
// TODO(Soremwar)
// `deno: boolean` is kept for backwards compatibility with the previous
// worker options implementation. Remove for 2.0
let workerDenoAttributes;
if (typeof deno == "boolean") {
workerDenoAttributes = {
// Change this to enable the Deno namespace by default
namespace: deno,
permissions: null,
};
let namespace;
let permissions;
if (typeof deno == "object") {
namespace = deno.namespace ?? false;
permissions = deno.permissions ?? undefined;
} else {
workerDenoAttributes = {
// Change this to enable the Deno namespace by default
namespace: !!(deno?.namespace ?? false),
permissions: (deno?.permissions ?? "inherit") === "inherit"
? null
: deno?.permissions,
};
// If the permission option is set to "none", all permissions
// must be removed from the worker
if (workerDenoAttributes.permissions === "none") {
workerDenoAttributes.permissions = {
env: false,
hrtime: false,
net: false,
ffi: false,
read: false,
run: false,
write: false,
};
}
// Assume `deno: boolean | undefined`.
// TODO(Soremwar)
// `deno: boolean` is kept for backwards compatibility with the previous
// worker options implementation. Remove for 2.0
namespace = !!deno;
permissions = undefined;
}
const workerType = webidl.converters["WorkerType"](type);
@ -218,17 +116,16 @@
specifier,
hasSourceCode,
sourceCode,
workerDenoAttributes.namespace,
workerDenoAttributes.permissions === null
? null
: parsePermissions(workerDenoAttributes.permissions),
options?.name,
namespace,
permissions,
name,
workerType,
);
this.#id = id;
this.#pollControl();
this.#pollMessages();
}
#handleError(e) {
const event = new ErrorEvent("error", {
cancelable: true,
@ -359,7 +256,6 @@
]);
window.__bootstrap.worker = {
parsePermissions,
Worker,
};
})(this);

View file

@ -3,10 +3,10 @@
((window) => {
const core = window.Deno.core;
const { parsePermissions } = window.__bootstrap.worker;
const { setExitHandler } = window.__bootstrap.os;
const { Console, inspectArgs } = window.__bootstrap.console;
const { metrics } = core;
const { serializePermissions } = window.__bootstrap.permissions;
const { assert } = window.__bootstrap.util;
const {
ArrayPrototypeFilter,
@ -230,7 +230,7 @@ finishing test case.`;
function pledgePermissions(permissions) {
return core.opSync(
"op_pledge_test_permissions",
parsePermissions(permissions),
serializePermissions(permissions),
);
}
@ -289,7 +289,7 @@ finishing test case.`;
if (testDef.permissions) {
testDef.fn = withPermissions(
testDef.fn,
parsePermissions(testDef.permissions),
testDef.permissions,
);
}

View file

@ -28,7 +28,6 @@ pub struct PermissionArgs {
host: Option<String>,
variable: Option<String>,
command: Option<String>,
library: Option<String>,
}
pub fn op_query_permission(
@ -50,7 +49,7 @@ pub fn op_query_permission(
),
"env" => permissions.env.query(args.variable.as_deref()),
"run" => permissions.run.query(args.command.as_deref()),
"ffi" => permissions.ffi.query(args.library.as_deref()),
"ffi" => permissions.ffi.query(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.query(),
n => {
return Err(custom_error(
@ -81,7 +80,7 @@ pub fn op_revoke_permission(
),
"env" => permissions.env.revoke(args.variable.as_deref()),
"run" => permissions.run.revoke(args.command.as_deref()),
"ffi" => permissions.ffi.revoke(args.library.as_deref()),
"ffi" => permissions.ffi.revoke(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.revoke(),
n => {
return Err(custom_error(
@ -112,7 +111,7 @@ pub fn op_request_permission(
),
"env" => permissions.env.request(args.variable.as_deref()),
"run" => permissions.run.request(args.command.as_deref()),
"ffi" => permissions.ffi.request(args.library.as_deref()),
"ffi" => permissions.ffi.request(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.request(),
n => {
return Err(custom_error(

View file

@ -1,18 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops::TestingFeaturesEnabled;
use crate::permissions::resolve_read_allowlist;
use crate::permissions::resolve_write_allowlist;
use crate::permissions::EnvDescriptor;
use crate::permissions::FfiDescriptor;
use crate::permissions::NetDescriptor;
use crate::permissions::PermissionState;
use crate::permissions::create_child_permissions;
use crate::permissions::ChildPermissionsArg;
use crate::permissions::Permissions;
use crate::permissions::ReadDescriptor;
use crate::permissions::RunDescriptor;
use crate::permissions::UnaryPermission;
use crate::permissions::UnitPermission;
use crate::permissions::WriteDescriptor;
use crate::web_worker::run_web_worker;
use crate::web_worker::SendableWebWorkerHandle;
use crate::web_worker::WebWorker;
@ -20,14 +11,10 @@ use crate::web_worker::WebWorkerHandle;
use crate::web_worker::WebWorkerType;
use crate::web_worker::WorkerControlEvent;
use crate::web_worker::WorkerId;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::op_async;
use deno_core::op_sync;
use deno_core::serde::de;
use deno_core::serde::de::SeqAccess;
use deno_core::serde::Deserialize;
use deno_core::serde::Deserializer;
use deno_core::Extension;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
@ -35,10 +22,6 @@ use deno_web::JsMessageData;
use log::debug;
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;
@ -131,369 +114,12 @@ pub fn init(create_web_worker_cb: Arc<CreateWebWorkerCb>) -> Extension {
.build()
}
fn merge_boolean_permission(
mut main: UnitPermission,
worker: Option<PermissionState>,
) -> Result<UnitPermission, AnyError> {
if let Some(worker) = worker {
if worker < main.state {
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.state = worker;
}
}
Ok(main)
}
fn merge_net_permission(
mut main: UnaryPermission<NetDescriptor>,
worker: Option<UnaryPermission<NetDescriptor>>,
) -> Result<UnaryPermission<NetDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(&(&x.0, x.1)).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_read_permission(
mut main: UnaryPermission<ReadDescriptor>,
worker: Option<UnaryPermission<ReadDescriptor>>,
) -> Result<UnaryPermission<ReadDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(x.0.as_path()).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_write_permission(
mut main: UnaryPermission<WriteDescriptor>,
worker: Option<UnaryPermission<WriteDescriptor>>,
) -> Result<UnaryPermission<WriteDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(x.0.as_path()).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_env_permission(
mut main: UnaryPermission<EnvDescriptor>,
worker: Option<UnaryPermission<EnvDescriptor>>,
) -> Result<UnaryPermission<EnvDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(x.as_ref()).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_run_permission(
mut main: UnaryPermission<RunDescriptor>,
worker: Option<UnaryPermission<RunDescriptor>>,
) -> Result<UnaryPermission<RunDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker.granted_list.iter().all(|x| main.check(&x.0).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_ffi_permission(
mut main: UnaryPermission<FfiDescriptor>,
worker: Option<UnaryPermission<FfiDescriptor>>,
) -> Result<UnaryPermission<FfiDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker.granted_list.iter().all(|x| main.check(&x.0).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
pub fn create_worker_permissions(
main_perms: Permissions,
worker_perms: PermissionsArg,
) -> Result<Permissions, AnyError> {
Ok(Permissions {
env: merge_env_permission(main_perms.env, worker_perms.env)?,
hrtime: merge_boolean_permission(main_perms.hrtime, worker_perms.hrtime)?,
net: merge_net_permission(main_perms.net, worker_perms.net)?,
ffi: merge_ffi_permission(main_perms.ffi, worker_perms.ffi)?,
read: merge_read_permission(main_perms.read, worker_perms.read)?,
run: merge_run_permission(main_perms.run, worker_perms.run)?,
write: merge_write_permission(main_perms.write, worker_perms.write)?,
})
}
#[derive(Debug, Deserialize)]
pub struct PermissionsArg {
#[serde(default, deserialize_with = "as_unary_env_permission")]
env: Option<UnaryPermission<EnvDescriptor>>,
#[serde(default, deserialize_with = "as_permission_state")]
hrtime: Option<PermissionState>,
#[serde(default, deserialize_with = "as_unary_net_permission")]
net: Option<UnaryPermission<NetDescriptor>>,
#[serde(default, deserialize_with = "as_unary_ffi_permission")]
ffi: Option<UnaryPermission<FfiDescriptor>>,
#[serde(default, deserialize_with = "as_unary_read_permission")]
read: Option<UnaryPermission<ReadDescriptor>>,
#[serde(default, deserialize_with = "as_unary_run_permission")]
run: Option<UnaryPermission<RunDescriptor>>,
#[serde(default, deserialize_with = "as_unary_write_permission")]
write: Option<UnaryPermission<WriteDescriptor>>,
}
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")
}
// visit_unit maps undefined/missing values to false
fn visit_unit<E>(self) -> Result<UnaryPermissionBase, E>
where
E: de::Error,
{
self.visit_bool(false)
}
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_net_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<NetDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
let allowed: HashSet<NetDescriptor> = value
.paths
.into_iter()
.map(NetDescriptor::from_string)
.collect();
Ok(Some(UnaryPermission::<NetDescriptor> {
global_state: value.global_state,
granted_list: allowed,
..Default::default()
}))
}
fn as_unary_read_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<ReadDescriptor>>, 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::<ReadDescriptor> {
global_state: value.global_state,
granted_list: resolve_read_allowlist(&Some(paths)),
..Default::default()
}))
}
fn as_unary_write_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<WriteDescriptor>>, 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::<WriteDescriptor> {
global_state: value.global_state,
granted_list: resolve_write_allowlist(&Some(paths)),
..Default::default()
}))
}
fn as_unary_env_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<EnvDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
Ok(Some(UnaryPermission::<EnvDescriptor> {
global_state: value.global_state,
granted_list: value.paths.into_iter().map(EnvDescriptor::new).collect(),
..Default::default()
}))
}
fn as_unary_run_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<RunDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
Ok(Some(UnaryPermission::<RunDescriptor> {
global_state: value.global_state,
granted_list: value.paths.into_iter().map(RunDescriptor).collect(),
..Default::default()
}))
}
fn as_unary_ffi_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<FfiDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
Ok(Some(UnaryPermission::<FfiDescriptor> {
global_state: value.global_state,
granted_list: value.paths.into_iter().map(FfiDescriptor).collect(),
..Default::default()
}))
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateWorkerArgs {
has_source_code: bool,
name: Option<String>,
permissions: Option<PermissionsArg>,
permissions: Option<ChildPermissionsArg>,
source_code: String,
specifier: String,
use_deno_namespace: bool,
@ -528,13 +154,18 @@ fn op_create_worker(
);
}
}
let parent_permissions = state.borrow::<Permissions>().clone();
let worker_permissions = if let Some(permissions) = args.permissions {
if args.permissions.is_some() {
super::check_unstable(state, "Worker.deno.permissions");
create_worker_permissions(parent_permissions.clone(), permissions)?
}
let parent_permissions = state.borrow_mut::<Permissions>();
let worker_permissions = if let Some(child_permissions_arg) = args.permissions
{
create_child_permissions(parent_permissions, child_permissions_arg)?
} else {
parent_permissions.clone()
};
let parent_permissions = parent_permissions.clone();
let worker_id = state.take::<WorkerId>();
let create_module_loader = state.take::<CreateWebWorkerCbHolder>();

File diff suppressed because it is too large Load diff