diff --git a/cli/dts/lib.deno.ns.d.ts b/cli/dts/lib.deno.ns.d.ts index 5b6849bfd8..05e600ed76 100644 --- a/cli/dts/lib.deno.ns.d.ts +++ b/cli/dts/lib.deno.ns.d.ts @@ -2034,6 +2034,12 @@ declare namespace Deno { * Environmental variables for subprocess can be specified using `opt.env` * mapping. * + * `opt.uid` sets the child process’s user ID. This translates to a setuid call + * in the child process. Failure in the setuid call will cause the spawn to fail. + * + * `opt.gid` is similar to `opt.uid`, but sets the group ID of the child process. + * This has the same semantics as the uid field. + * * By default subprocess inherits stdio of parent process. To change that * `opt.stdout`, `opt.stderr` and `opt.stdin` can be specified independently - * they can be set to either an rid of open file or set to "inherit" "piped" diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 5854a51812..1a27f2b389 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -713,8 +713,12 @@ declare namespace Deno { export function run< T extends RunOptions & { clearEnv?: boolean; + gid?: number; + uid?: number; } = RunOptions & { clearEnv?: boolean; + gid?: number; + uid?: number; }, >(opt: T): Process; diff --git a/cli/tests/unit/process_test.ts b/cli/tests/unit/process_test.ts index 371521e332..9f8e9cd9fc 100644 --- a/cli/tests/unit/process_test.ts +++ b/cli/tests/unit/process_test.ts @@ -537,3 +537,59 @@ unitTest( p.close(); }, ); + +unitTest( + { perms: { run: true, read: true }, ignore: Deno.build.os === "windows" }, + async function uid(): Promise { + const p = Deno.run({ + cmd: [ + "id", + "-u", + ], + stdout: "piped", + }); + + const currentUid = new TextDecoder().decode(await p.output()); + p.close(); + + if (currentUid !== "0") { + assertThrows(() => { + Deno.run({ + cmd: [ + "echo", + "fhqwhgads", + ], + uid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); + +unitTest( + { perms: { run: true, read: true }, ignore: Deno.build.os === "windows" }, + async function gid(): Promise { + const p = Deno.run({ + cmd: [ + "id", + "-g", + ], + stdout: "piped", + }); + + const currentGid = new TextDecoder().decode(await p.output()); + p.close(); + + if (currentGid !== "0") { + assertThrows(() => { + Deno.run({ + cmd: [ + "echo", + "fhqwhgads", + ], + gid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); diff --git a/runtime/js/40_process.js b/runtime/js/40_process.js index 543c53c271..782dfe4764 100644 --- a/runtime/js/40_process.js +++ b/runtime/js/40_process.js @@ -102,6 +102,8 @@ cwd = undefined, clearEnv = false, env = {}, + gid = undefined, + uid = undefined, stdout = "inherit", stderr = "inherit", stdin = "inherit", @@ -114,6 +116,8 @@ cwd, clearEnv, env: ObjectEntries(env), + gid, + uid, stdin: isRid(stdin) ? "" : stdin, stdout: isRid(stdout) ? "" : stdout, stderr: isRid(stderr) ? "" : stderr, diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index b47492e58e..e40a15b4bc 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -63,6 +63,8 @@ pub struct RunArgs { cwd: Option, clear_env: bool, env: Vec<(String, String)>, + gid: Option, + uid: Option, stdin: String, stdout: String, stderr: String, @@ -123,6 +125,24 @@ fn op_run( c.env(key, value); } + #[cfg(unix)] + if let Some(gid) = run_args.gid { + super::check_unstable(state, "Deno.run.gid"); + c.gid(gid); + } + #[cfg(unix)] + if let Some(uid) = run_args.uid { + super::check_unstable(state, "Deno.run.uid"); + c.uid(uid); + } + #[cfg(unix)] + unsafe { + c.pre_exec(|| { + libc::setgroups(0, std::ptr::null()); + Ok(()) + }); + } + // TODO: make this work with other resources, eg. sockets if !run_args.stdin.is_empty() { c.stdin(subprocess_stdio_map(run_args.stdin.as_ref())?);