{"version":3,"file":"sandboxUser.cjs","names":["os","child_process","fs","path"],"sources":["../../src/helpers/sandboxUser.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/**\n * Name of the environment variable through which a judge server tells problem-utils to run\n * untrusted submitted programs as the given unprivileged OS user via sudo.\n *\n * The judge server sets this variable only when it runs the judge harness (e.g. `judge.ts`) as its\n * own trusted user, so that problem files (test cases and the harness itself) stay unreadable to\n * submissions while the submissions themselves run under the sandbox user. When the variable is\n * absent or empty (local development, course authoring, or an all-sandbox judge run), commands run\n * as the current user like before.\n *\n * Contract for the delegating judge server (delegation is Linux-only — sudo user separation and\n * `/home/<user>` homes are provisioned in the judge Docker image; never set this on macOS):\n * - The harness process environment is forwarded to sandboxed submissions (sudo runs with\n *   `--preserve-env`), so it must not contain secrets beyond what submissions may see. The one\n *   exception is the set glibc strips when executing the setuid `sudo` (secure-execution mode:\n *   `LD_*`, `TMPDIR`, `LOCPATH`, `NLSPATH`, `TZDIR`, … — see ld.so(8)); no sudoers setting can\n *   recover those, and only `LD_LIBRARY_PATH` is restored, by the wrapper below.\n * - sudoers must let the harness user run arbitrary commands as the sandbox user without a\n *   password, pass the environment through, and keep sudo off a pseudo-terminal (a pty would merge\n *   stderr into stdout and CRLF-mangle output when the harness happens to run from a terminal):\n *   e.g. `Defaults:<harness> !env_reset, !env_delete, !env_check, !secure_path, !use_pty` plus\n *   `<harness> ALL=(<sandbox>) NOPASSWD:SETENV: ALL`.\n * - Variables meant for the submitted program rather than for the harness must be passed under\n *   {@link SANDBOX_ENV_PREFIX}; problem-utils strips that prefix when it builds a submission's\n *   environment.\n * - The sandbox user's home directory must exist at `/home/<sandbox user>` and be writable. It is\n *   shared across sequential requests, so the server is responsible for resetting whatever\n *   cross-request persistence there matters to it.\n */\nexport const SANDBOX_USER_ENV_NAME = 'EXERCODE_SANDBOX_USER';\n\n// Absolute paths so a submission-influenced `PATH` cannot redirect binaries that the trusted\n// harness user executes (`sudo` is also setuid and must be the real one).\nconst SUDO_PATH = '/usr/bin/sudo';\nconst CHMOD_PATH = '/bin/chmod';\nconst SLEEP_PATH = '/bin/sleep';\n// Minimal environment for trusted helper processes; never forward the harness environment to them.\nconst MINIMAL_ENV = { PATH: '/usr/local/bin:/usr/bin:/bin' } as const;\n\n/**\n * Environment for helper processes the *trusted* harness user runs (`ps`, `xwininfo`, `Xvfb`, …).\n * The judge server overlays caller-supplied variables onto the harness environment, and a sandboxed\n * submission can create executables in its persistent home, so resolving these helpers through the\n * inherited `PATH` would hand the submission code execution as the harness user. Node resolves the\n * command through the child environment's `PATH`, so a fixed `PATH` here pins them to system paths.\n */\nexport function getTrustedHelperEnv(extraEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n  // Without delegation the submission runs as this same user, so there is no boundary to defend and\n  // pinning `PATH` would only break setups whose X11/`ps` binaries live elsewhere (e.g. /usr/games,\n  // /snap/bin, Nix). Keep the inherited environment there, exactly as before delegation existed.\n  if (!sandboxUserName) return { ...process.env, ...extraEnv };\n  // `PATH` last: a caller passing a whole environment through would otherwise reinstate the\n  // submission-influenced one this function exists to replace.\n  return { ...extraEnv, PATH: MINIMAL_ENV.PATH };\n}\n\nexport const sandboxUserName = process.env[SANDBOX_USER_ENV_NAME] || undefined;\n\n// Fail fast on the catastrophic misconfiguration where the sandbox user is the harness's own user:\n// cleanup (`killSandboxUserProcesses`) would then SIGKILL the harness itself on the first run.\nif (sandboxUserName && sandboxUserName === os.userInfo().username) {\n  throw new Error(\n    `${SANDBOX_USER_ENV_NAME} must name a different OS user than the one running the harness (got \"${sandboxUserName}\"). Leave it unset to run everything as the current user.`\n  );\n}\n\n/**\n * The deadline supervisor. Absolute under delegation: the wrapper's `exec \"$0\"` resolves it through\n * the submission's own `PATH` (which a judge server may set via {@link SANDBOX_ENV_PREFIX}), so a\n * bare name would let a submission replace the very process that enforces its time limit. The bare\n * name is kept without delegation, where `timeout` may live elsewhere (Homebrew coreutils).\n */\nexport const TIMEOUT_COMMAND = sandboxUserName ? '/usr/bin/timeout' : 'timeout';\n\n/**\n * Wrap a command so it runs as the sandbox user. `umask 0` makes every file the sandboxed process\n * creates world-writable, so the harness user can clean it up without privileges. The wrapper also\n * restores `LD_LIBRARY_PATH`, which ld.so strips across the setuid `sudo` exec. Put `timeout`\n * inside the wrapped command: the harness user cannot signal the root-owned `sudo` process, so an\n * outer timer alone could not stop a runaway submission.\n */\nexport function wrapCommandWithSandboxUser(command: readonly [string, ...string[]]): [string, ...string[]] {\n  if (!sandboxUserName) return [...command];\n  // Idempotent; see {@link isSandboxWrappedCommand}.\n  if (isSandboxWrappedCommand(command)) return [...command];\n  return [...buildSandboxWrapperPrefix(sandboxUserName), ...command];\n}\n\nconst SANDBOX_WRAPPER_SCRIPT =\n  'umask 0; if [ -n \"$SANDBOX_LD_LIBRARY_PATH\" ]; then export LD_LIBRARY_PATH=\"$SANDBOX_LD_LIBRARY_PATH\"; fi; exec \"$0\" \"$@\"';\n\nfunction buildSandboxWrapperPrefix(user: string): [string, ...string[]] {\n  return [SUDO_PATH, '--preserve-env', '-u', user, '--', 'sh', '-c', SANDBOX_WRAPPER_SCRIPT];\n}\n\n/**\n * Whether the command was already wrapped by {@link wrapCommandWithSandboxUser}. The presets hand\n * custom runners a wrapped command, and a runner may forward it to another helper that wraps too;\n * a nested wrapper's inner `sudo` would run AS the sandbox user, which sudoers does not authorize,\n * so every test case of such a problem would fail only under delegation.\n *\n * Matches the wrapper prefix exactly rather than looking for `sudo` anywhere: a submission-derived\n * command could otherwise carry a literal `/usr/bin/sudo` argument and skip wrapping entirely,\n * which would run the submission as the trusted harness user.\n */\nexport function isSandboxWrappedCommand(command: readonly string[]): boolean {\n  if (!sandboxUserName) return false;\n  const prefix = buildSandboxWrapperPrefix(sandboxUserName);\n  return prefix.every((argument, index) => command[index] === argument);\n}\n\n/**\n * Insert `innerPrefix` (e.g. a `time` measurement prefix) so that it runs INSIDE the sandbox\n * wrapper when `command` is wrapped, and simply in front otherwise. Prefixing a wrapped command\n * from the outside would run `innerPrefix` as the trusted harness user while its arguments (such\n * as an output path) point into a sandbox-writable directory, where a submission can plant a\n * symlink and have the harness truncate an arbitrary file it owns.\n */\nexport function prependInsideSandboxWrapper(\n  command: readonly [string, ...string[]],\n  innerPrefix: readonly string[]\n): [string, ...string[]] {\n  if (!isSandboxWrappedCommand(command)) return [...innerPrefix, ...command] as [string, ...string[]];\n  const prefixLength = buildSandboxWrapperPrefix(sandboxUserName as string).length;\n  return [...command.slice(0, prefixLength), ...innerPrefix, ...command.slice(prefixLength)] as [string, ...string[]];\n}\n\n/**\n * Environment overrides for sandboxed processes: a writable home and the `LD_LIBRARY_PATH`\n * smuggled past the setuid `sudo` exec (see {@link wrapCommandWithSandboxUser}). Pass the\n * environment the command will actually run with so a caller-supplied `LD_LIBRARY_PATH` survives\n * the exec; the harness's own value is only the fallback.\n */\nexport function getSandboxUserEnvOverrides(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n  if (!sandboxUserName) return {};\n  const sourceEnv = env ?? process.env;\n  const submissionOnlyEnv = unwrapSubmissionOnlyEnv(sourceEnv);\n  // A submission-only `LD_LIBRARY_PATH` must be the one smuggled past the exec, not the harness's.\n  const ldLibraryPath = submissionOnlyEnv.LD_LIBRARY_PATH ?? sourceEnv.LD_LIBRARY_PATH ?? process.env.LD_LIBRARY_PATH;\n  return {\n    HOME: `/home/${sandboxUserName}`,\n    ...submissionOnlyEnv,\n    // Last: the wrapper reads this one, so a caller must not be able to overwrite it.\n    ...(ldLibraryPath && { SANDBOX_LD_LIBRARY_PATH: ldLibraryPath }),\n  };\n}\n\n/**\n * Prefix under which a delegating judge server passes variables that belong to the submitted\n * program alone. Applying a request's variables to the harness itself would let a submission point\n * e.g. `PATH` or `NODE_OPTIONS` at its own files and get code executed as the trusted harness user,\n * so the server prefixes them and problem-utils strips the prefix back off here, where the\n * environment of a sandboxed submission is built.\n */\nexport const SANDBOX_ENV_PREFIX = 'SANDBOX_ENV_';\n\nfunction unwrapSubmissionOnlyEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n  const unwrapped: NodeJS.ProcessEnv = {};\n  for (const [name, value] of Object.entries(env)) {\n    if (!name.startsWith(SANDBOX_ENV_PREFIX)) continue;\n    const unwrappedName = name.slice(SANDBOX_ENV_PREFIX.length);\n    if (unwrappedName) unwrapped[unwrappedName] = value;\n  }\n  return unwrapped;\n}\n\n/**\n * Make harness-user-created files under the given path readable, and directories writable, for the\n * sandbox user, so sandboxed programs can read their sources and create outputs next to them.\n */\nexport function makeAccessibleToSandboxUser(targetPath: string): void {\n  if (!sandboxUserName) return;\n  // Files owned by the sandbox user fail to chmod but are already world-writable (umask 0).\n  child_process.spawnSync(CHMOD_PATH, ['-R', 'a+rwX', targetPath], { env: MINIMAL_ENV });\n}\n\n/**\n * Kill the sandbox user's processes with the given signals. The harness user cannot signal another\n * user's processes (nor the root-owned `sudo` wrapper), so this goes through sudo. Killing every\n * sandbox process is safe because the judge server handles one request at a time. The default\n * sends SIGTERM immediately followed by SIGKILL (final cleanup); callers that want a grace period\n * send `['TERM']`, wait, and then send `['KILL']`.\n *\n * Fails closed: a `['TERM']`-only sweep throws when its `sudo` could not be spawned (e.g. a\n * submission exhausted the PID cgroup); a sweep including SIGKILL throws when a sandbox process is\n * still alive, or cannot be listed, afterwards. Reporting success would leave that process running\n * next to the next request on this instance.\n */\nexport function killSandboxUserProcesses(signals: readonly ('TERM' | 'KILL')[] = ['TERM', 'KILL']): void {\n  if (!sandboxUserName) return;\n  // One direct call per signal instead of one `sh -c 'pkill ...; pkill ...'`: the wrapping shell\n  // would run as the sandbox user too, so the first pkill would kill it before the second ran.\n  // Exit statuses are not inspected: `sudo` exits with 1 both when `pkill` matched nothing and on\n  // its own failures, and a sweep can itself be killed by a concurrent watchdog sweep\n  // ({@link startSandboxTimeoutWatchdog}'s `pkill -KILL` matches it). What matters is the outcome.\n  const spawnErrors: string[] = [];\n  for (const signal of signals) {\n    const result = runAsSandboxUser(['pkill', `-${signal}`, '-u', sandboxUserName]);\n    if (result.error) spawnErrors.push(`pkill -${signal}: ${result.error.message}`);\n  }\n  if (!signals.includes('KILL')) {\n    // Survivors are expected during the caller's grace period, so only a sweep that never started\n    // is a failure here.\n    if (spawnErrors.length > 0) {\n      throw new Error(`failed to signal ${sandboxUserName} processes: ${spawnErrors.join('; ')}`);\n    }\n    return;\n  }\n  const survivors = findSurvivingSandboxUserProcesses();\n  if (survivors) throw new Error(`failed to terminate ${sandboxUserName} processes: ${survivors}`);\n}\n\n/**\n * Lists the sandbox user's live processes as the harness user (listing needs no privilege), or\n * `undefined` when none remain. SIGKILL is re-sent between attempts: a child forked after `pkill`\n * scanned `/proc` was never signalled, and a process in uninterruptible sleep dies only once its\n * I/O completes. Just-killed processes linger as zombies until reaped; a zombie row is ignored only\n * when it has a single thread, because a thread-group leader that exited via `pthread_exit` shows\n * as a zombie while its other threads keep running.\n */\nfunction findSurvivingSandboxUserProcesses(): string | undefined {\n  for (let attempt = 0; ; attempt++) {\n    if (attempt > 0) {\n      runAsSandboxUser(['pkill', '-KILL', '-u', sandboxUserName as string]);\n      Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);\n    }\n    const result = child_process.spawnSync('ps', ['-o', 'pid=,stat=,nlwp=,comm=', '-u', sandboxUserName as string], {\n      env: MINIMAL_ENV,\n      encoding: 'utf8',\n    });\n    // `ps` exits with 1 both when no process matched and on its own errors, which it reports on\n    // stderr; anything else is a failure to list, which must count as a failure to verify.\n    const stderr = result.stderr?.trim();\n    if (result.error) return `cannot list processes: ${result.error.message}`;\n    if (result.signal || (result.status !== 0 && (result.status !== 1 || stderr))) {\n      return `cannot list processes: ${stderr || `ps exited with ${result.status ?? result.signal}`}`;\n    }\n    const survivors = result.stdout\n      .split('\\n')\n      .map((line) => line.trim())\n      .filter((line) => line && !/^\\d+\\s+Z\\S*\\s+1\\s/.test(line));\n    if (survivors.length === 0) return undefined;\n    if (attempt >= 10) return survivors.join(', ');\n  }\n}\n\n/**\n * Let the sandbox user reopen the permissions of its own files under the given path, for\n * harness-side traversal/cleanup of trees where a sandboxed process restricted permissions\n * (some tools chmod their outputs regardless of umask).\n */\nexport function relaxPermissionsAsSandboxUser(targetPath: string): void {\n  if (!sandboxUserName) return;\n  runAsSandboxUser(['chmod', '-R', 'a+rwX', targetPath]);\n}\n\n/**\n * How long after a command's own deadline {@link startSandboxTimeoutWatchdog} force-kills the\n * sandbox user. Callers waiting for the watchdog to end a submission must wait at least this long.\n */\nexport const SANDBOX_WATCHDOG_GRACE_SECONDS = 5;\n\n/** Cancels a {@link startSandboxTimeoutWatchdog}; `fired` reports whether its deadline elapsed. */\nexport interface SandboxTimeoutWatchdog {\n  cancel(): void;\n  fired(): boolean;\n}\n\n/**\n * Start a harness-owned watchdog that force-kills every sandbox process after the given deadline.\n * A sandboxed submission can signal its own `timeout` supervisor (same UID), and a synchronous\n * spawn blocks the harness's event loop, so without this external deadline a submission could run\n * until the outer judge-server limit. The watchdog's `sh`/`sleep` run as the harness user, out of\n * the submission's reach; killing the watchdog's process group cancels it before it spawns sudo.\n *\n * ALWAYS cancel in a `finally`: the watchdog is detached and `unref`'d, so a leaked one keeps\n * running after the harness exits and would SIGKILL a later request's submission.\n */\nexport function startSandboxTimeoutWatchdog(timeoutSeconds: number): SandboxTimeoutWatchdog {\n  if (!sandboxUserName) return { cancel: () => {}, fired: () => false };\n\n  const deadlineSeconds = Math.ceil(timeoutSeconds) + SANDBOX_WATCHDOG_GRACE_SECONDS;\n  const watchdog = child_process.spawn(\n    '/bin/sh',\n    ['-c', `${SLEEP_PATH} ${deadlineSeconds}; ${SUDO_PATH} -u \"$1\" pkill -KILL -u \"$1\"`, 'sh', sandboxUserName],\n    { detached: true, stdio: 'ignore', env: MINIMAL_ENV }\n  );\n  // Without a listener, a spawn failure (e.g. the submission exhausted the PID cgroup) would be an\n  // unhandled 'error' event that terminates the harness. Fail closed instead: callers check\n  // `fired()`, and a watchdog that never started reports its deadline as elapsed.\n  let spawnFailed = false;\n  watchdog.on('error', () => {\n    spawnFailed = true;\n  });\n  let exited = false;\n  watchdog.on('exit', () => {\n    exited = true;\n  });\n  watchdog.unref();\n\n  const startTimeMilliseconds = Date.now();\n  let cancelled = false;\n  return {\n    cancel: () => {\n      cancelled = true;\n      if (watchdog.pid === undefined || exited) return;\n      try {\n        process.kill(-watchdog.pid, 'SIGKILL');\n      } catch {\n        // The watchdog already fired and exited.\n      }\n    },\n    // The `exit`/`error` events cannot be observed from a synchronous caller (they need the event\n    // loop), so decide by elapsed time, which is what the watchdog itself waits on.\n    fired: () =>\n      spawnFailed ||\n      watchdog.pid === undefined ||\n      (!cancelled && Date.now() - startTimeMilliseconds >= deadlineSeconds * 1000),\n  };\n}\n\n/**\n * Run a helper as the sandbox user with a minimal environment. The harness environment must not be\n * passed: sudoers forwards it verbatim, and after sudo drops privileges the helper's\n * `/proc/<pid>/environ` becomes readable by every other sandbox process.\n */\nfunction runAsSandboxUser(command: [string, ...string[]]): child_process.SpawnSyncReturns<Buffer> {\n  return child_process.spawnSync(SUDO_PATH, ['-u', sandboxUserName as string, ...command], { env: MINIMAL_ENV });\n}\n\n/**\n * `fs.rm`-like removal with a fallback for trees holding sandbox-user-owned entries whose\n * permissions block deletion: let the sandbox user reopen its own files first, then retry. The\n * containing directory is relaxed as well — unlinking an entry needs write permission on its\n * parent, so a submission that chmods a directory it owns would otherwise make everything inside it\n * undeletable.\n */\nexport async function forceRemove(targetPath: string): Promise<void> {\n  try {\n    await fs.promises.rm(targetPath, { force: true, recursive: true });\n  } catch (error) {\n    if (!sandboxUserName) throw error;\n    relaxPermissionsAsSandboxUser(targetPath);\n    // Not recursive: only the containing directory's own write bit governs the unlink.\n    runAsSandboxUser(['chmod', 'a+rwX', path.dirname(targetPath)]);\n    await fs.promises.rm(targetPath, { force: true, recursive: true });\n  }\n}\n"],"mappings":"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"}