Repository: denoland/deno
Pull request: fix(ext/node): implement `process.setgid()` and `process.setuid()`

Closes #31145, #31146

Changed files:
filename: ext/node/lib.rs
patch: @@ -459,6 +459,8 @@ deno_core::extension!(deno_node,
     ops::process::op_node_process_kill,
     ops::process::op_node_process_setegid<P>,
     ops::process::op_node_process_seteuid<P>,
+    ops::process::op_node_process_setgid<P>,
+    ops::process::op_node_process_setuid<P>,
     ops::process::op_process_abort,
     ops::tls::op_get_root_certificates,
     ops::tls::op_tls_peer_certificate,

filename: ext/node/ops/process.rs
patch: @@ -209,3 +209,79 @@ where
 {
   Err(ProcessError::NotSupported)
 }
+
+#[cfg(not(any(target_os = "android", target_os = "windows")))]
+#[op2(fast, stack_trace)]
+pub fn op_node_process_setgid<P, 'a>(
+  scope: &mut v8::PinScope<'a, '_>,
+  state: &mut OpState,
+  id: v8::Local<'a, v8::Value>,
+) -> Result<(), ProcessError>
+where
+  P: NodePermissions + 'static,
+{
+  {
+    let permissions = state.borrow_mut::<P>();
+    permissions.check_sys("setgid", "node:process.setgid")?;
+  }
+
+  let gid = match serialize_id(scope, id)? {
+    Id::Number(number) => Gid::from_raw(number),
+    Id::Name(name) => get_group_id(&name)?,
+  };
+
+  nix::unistd::setgid(gid)?;
+
+  Ok(())
+}
+
+#[cfg(any(target_os = "android", target_os = "windows"))]
+#[op2(fast, stack_trace)]
+pub fn op_node_process_setgid<P>(
+  _scope: &mut v8::PinScope<'_, '_>,
+  _state: &mut OpState,
+  _id: v8::Local<'_, v8::Value>,
+) -> Result<(), ProcessError>
+where
+  P: NodePermissions + 'static,
+{
+  Err(ProcessError::NotSupported)
+}
+
+#[cfg(not(any(target_os = "android", target_os = "windows")))]
+#[op2(fast, stack_trace)]
+pub fn op_node_process_setuid<P, 'a>(
+  scope: &mut v8::PinScope<'a, '_>,
+  state: &mut OpState,
+  id: v8::Local<'a, v8::Value>,
+) -> Result<(), ProcessError>
+where
+  P: NodePermissions + 'static,
+{
+  {
+    let permissions = state.borrow_mut::<P>();
+    permissions.check_sys("setuid", "node:process.setuid")?;
+  }
+
+  let uid = match serialize_id(scope, id)? {
+    Id::Number(number) => Uid::from_raw(number),
+    Id::Name(name) => get_user_id(&name)?,
+  };
+
+  nix::unistd::setuid(uid)?;
+
+  Ok(())
+}
+
+#[cfg(any(target_os = "android", target_os = "windows"))]
+#[op2(fast, stack_trace)]
+pub fn op_node_process_setuid<P>(
+  _scope: &mut v8::PinScope<'_, '_>,
+  _state: &mut OpState,
+  _id: v8::Local<'_, v8::Value>,
+) -> Result<(), ProcessError>
+where
+  P: NodePermissions + 'static,
+{
+  Err(ProcessError::NotSupported)
+}

filename: ext/node/polyfills/process.ts
patch: @@ -14,6 +14,8 @@ import {
   op_node_process_kill,
   op_node_process_setegid,
   op_node_process_seteuid,
+  op_node_process_setgid,
+  op_node_process_setuid,
   op_process_abort,
 } from "ext:core/ops";
 
@@ -379,7 +381,7 @@ export function kill(pid: number, sig: string | number = "SIGTERM") {
   return true;
 }
 
-let getgid, getuid, getegid, geteuid, setegid, seteuid;
+let getgid, getuid, getegid, geteuid, setegid, seteuid, setgid, setuid;
 
 function wrapIdSetter(
   syscall: string,
@@ -410,10 +412,12 @@ if (!isWindows) {
   if (!isAndroid) {
     setegid = wrapIdSetter("setegid", op_node_process_setegid);
     seteuid = wrapIdSetter("seteuid", op_node_process_seteuid);
+    setgid = wrapIdSetter("setgid", op_node_process_setgid);
+    setuid = wrapIdSetter("setuid", op_node_process_setuid);
   }
 }
 
-export { getegid, geteuid, getgid, getuid, setegid, seteuid };
+export { getegid, geteuid, getgid, getuid, setegid, seteuid, setgid, setuid };
 
 const ALLOWED_FLAGS = buildAllowedFlags();
 
@@ -828,6 +832,12 @@ process.setegid = setegid;
 /** This method is removed on Windows */
 process.seteuid = seteuid;
 
+/** This method is removed on Windows */
+process.setgid = setgid;
+
+/** This method is removed on Windows */
+process.setuid = setuid;
+
 process.getBuiltinModule = getBuiltinModule;
 
 // TODO(kt3k): Implement this when we added -e option to node compat mode

filename: runtime/permissions/lib.rs
patch: @@ -2441,7 +2441,9 @@ impl SysDescriptor {
       "hostname" | "inspector" | "osRelease" | "osUptime" | "loadavg"
       | "networkInterfaces" | "systemMemoryInfo" | "uid" | "gid" | "cpus"
       | "homedir" | "getegid" | "statfs" | "getPriority" | "setPriority"
-      | "userInfo" | "setegid" | "seteuid" => Ok(Self(kind)),
+      | "userInfo" | "setegid" | "seteuid" | "setgid" | "setuid" => {
+        Ok(Self(kind))
+      }
 
       // the underlying permission check changed to `userInfo` to better match the API,
       // alias this to avoid breaking existing projects with `--allow-sys=username`

filename: tests/unit_node/process_test.ts
patch: @@ -12,10 +12,14 @@ import process, {
   execPath as importedExecPath,
   getegid,
   geteuid,
+  getgid,
+  getuid,
   pid as importedPid,
   platform as importedPlatform,
   setegid,
   seteuid,
+  setgid,
+  setuid,
 } from "node:process";
 
 import { Readable } from "node:stream";
@@ -1425,3 +1429,71 @@ Deno.test({
     assertEquals(process.seteuid, undefined);
   },
 });
+
+Deno.test({
+  name: "process.setgid()",
+  ignore: Deno.build.os === "windows" || Deno.build.os === "android",
+  fn() {
+    const originalGid = getgid!();
+    const groupName = getGroupNameFromSystem(originalGid);
+
+    // Calling with original gid to ensure it doesn't throw
+    setgid!(originalGid);
+    setgid!(groupName);
+  },
+});
+
+Deno.test({
+  name: "process.setgid() throws on invalid group",
+  ignore: Deno.build.os === "windows" || Deno.build.os === "android",
+  fn() {
+    assertThrows(
+      () => {
+        setgid!("67?!");
+      },
+      "Group identifier does not exist: 67?!",
+    );
+  },
+});
+
+Deno.test({
+  name: "process.setgid() should be undefined on unsupported platforms",
+  ignore: Deno.build.os !== "windows" && Deno.build.os !== "android",
+  fn() {
+    assertEquals(process.setgid, undefined);
+  },
+});
+
+Deno.test({
+  name: "process.setuid()",
+  ignore: Deno.build.os === "windows" || Deno.build.os === "android",
+  fn() {
+    const originalUid = getuid!();
+    const userName = getUserNameFromSystem(originalUid);
+
+    // Calling with original uid to ensure it doesn't throw
+    setuid!(originalUid);
+    setuid!(userName);
+  },
+});
+
+Deno.test({
+  name: "process.setuid() throws on invalid user",
+  ignore: Deno.build.os === "windows" || Deno.build.os === "android",
+  fn() {
+    assertThrows(
+      () => {
+        setuid!("67?!");
+      },
+      "User identifier does not exist: 67?!",
+    );
+  },
+});
+
+Deno.test({
+  name: "process.setuid() should be undefined on unsupported platforms",
+  ignore: Deno.build.os !== "windows" && Deno.build.os !== "android",
+  fn() {
+    assertEquals(process.setuid, undefined);
+  },
+});