/** * Name of the environment variable through which a judge server tells problem-utils to run * untrusted submitted programs as the given unprivileged OS user via sudo. * * The judge server sets this variable only when it runs the judge harness (e.g. `judge.ts`) as its * own trusted user, so that problem files (test cases and the harness itself) stay unreadable to * submissions while the submissions themselves run under the sandbox user. When the variable is * absent or empty (local development, course authoring, or an all-sandbox judge run), commands run * as the current user like before. * * Contract for the delegating judge server (delegation is Linux-only — sudo user separation and * `/home/` homes are provisioned in the judge Docker image; never set this on macOS): * - The harness process environment is forwarded to sandboxed submissions (sudo runs with * `--preserve-env`), so it must not contain secrets beyond what submissions may see. The one * exception is the set glibc strips when executing the setuid `sudo` (secure-execution mode: * `LD_*`, `TMPDIR`, `LOCPATH`, `NLSPATH`, `TZDIR`, … — see ld.so(8)); no sudoers setting can * recover those, and only `LD_LIBRARY_PATH` is restored, by the wrapper below. * - sudoers must let the harness user run arbitrary commands as the sandbox user without a * password, pass the environment through, and keep sudo off a pseudo-terminal (a pty would merge * stderr into stdout and CRLF-mangle output when the harness happens to run from a terminal): * e.g. `Defaults: !env_reset, !env_delete, !env_check, !secure_path, !use_pty` plus * ` ALL=() NOPASSWD:SETENV: ALL`. * - Variables meant for the submitted program rather than for the harness must be passed under * {@link SANDBOX_ENV_PREFIX}; problem-utils strips that prefix when it builds a submission's * environment. * - The sandbox user's home directory must exist at `/home/` and be writable. It is * shared across sequential requests, so the server is responsible for resetting whatever * cross-request persistence there matters to it. */ export declare const SANDBOX_USER_ENV_NAME = "EXERCODE_SANDBOX_USER"; /** * Environment for helper processes the *trusted* harness user runs (`ps`, `xwininfo`, `Xvfb`, …). * The judge server overlays caller-supplied variables onto the harness environment, and a sandboxed * submission can create executables in its persistent home, so resolving these helpers through the * inherited `PATH` would hand the submission code execution as the harness user. Node resolves the * command through the child environment's `PATH`, so a fixed `PATH` here pins them to system paths. */ export declare function getTrustedHelperEnv(extraEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv; export declare const sandboxUserName: string | undefined; /** * The deadline supervisor. Absolute under delegation: the wrapper's `exec "$0"` resolves it through * the submission's own `PATH` (which a judge server may set via {@link SANDBOX_ENV_PREFIX}), so a * bare name would let a submission replace the very process that enforces its time limit. The bare * name is kept without delegation, where `timeout` may live elsewhere (Homebrew coreutils). */ export declare const TIMEOUT_COMMAND: string; /** * Wrap a command so it runs as the sandbox user. `umask 0` makes every file the sandboxed process * creates world-writable, so the harness user can clean it up without privileges. The wrapper also * restores `LD_LIBRARY_PATH`, which ld.so strips across the setuid `sudo` exec. Put `timeout` * inside the wrapped command: the harness user cannot signal the root-owned `sudo` process, so an * outer timer alone could not stop a runaway submission. */ export declare function wrapCommandWithSandboxUser(command: readonly [string, ...string[]]): [string, ...string[]]; /** * Whether the command was already wrapped by {@link wrapCommandWithSandboxUser}. The presets hand * custom runners a wrapped command, and a runner may forward it to another helper that wraps too; * a nested wrapper's inner `sudo` would run AS the sandbox user, which sudoers does not authorize, * so every test case of such a problem would fail only under delegation. * * Matches the wrapper prefix exactly rather than looking for `sudo` anywhere: a submission-derived * command could otherwise carry a literal `/usr/bin/sudo` argument and skip wrapping entirely, * which would run the submission as the trusted harness user. */ export declare function isSandboxWrappedCommand(command: readonly string[]): boolean; /** * Insert `innerPrefix` (e.g. a `time` measurement prefix) so that it runs INSIDE the sandbox * wrapper when `command` is wrapped, and simply in front otherwise. Prefixing a wrapped command * from the outside would run `innerPrefix` as the trusted harness user while its arguments (such * as an output path) point into a sandbox-writable directory, where a submission can plant a * symlink and have the harness truncate an arbitrary file it owns. */ export declare function prependInsideSandboxWrapper(command: readonly [string, ...string[]], innerPrefix: readonly string[]): [string, ...string[]]; /** * Environment overrides for sandboxed processes: a writable home and the `LD_LIBRARY_PATH` * smuggled past the setuid `sudo` exec (see {@link wrapCommandWithSandboxUser}). Pass the * environment the command will actually run with so a caller-supplied `LD_LIBRARY_PATH` survives * the exec; the harness's own value is only the fallback. */ export declare function getSandboxUserEnvOverrides(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv; /** * Prefix under which a delegating judge server passes variables that belong to the submitted * program alone. Applying a request's variables to the harness itself would let a submission point * e.g. `PATH` or `NODE_OPTIONS` at its own files and get code executed as the trusted harness user, * so the server prefixes them and problem-utils strips the prefix back off here, where the * environment of a sandboxed submission is built. */ export declare const SANDBOX_ENV_PREFIX = "SANDBOX_ENV_"; /** * Make harness-user-created files under the given path readable, and directories writable, for the * sandbox user, so sandboxed programs can read their sources and create outputs next to them. */ export declare function makeAccessibleToSandboxUser(targetPath: string): void; /** * Kill the sandbox user's processes with the given signals. The harness user cannot signal another * user's processes (nor the root-owned `sudo` wrapper), so this goes through sudo. Killing every * sandbox process is safe because the judge server handles one request at a time. The default * sends SIGTERM immediately followed by SIGKILL (final cleanup); callers that want a grace period * send `['TERM']`, wait, and then send `['KILL']`. * * Fails closed: a `['TERM']`-only sweep throws when its `sudo` could not be spawned (e.g. a * submission exhausted the PID cgroup); a sweep including SIGKILL throws when a sandbox process is * still alive, or cannot be listed, afterwards. Reporting success would leave that process running * next to the next request on this instance. */ export declare function killSandboxUserProcesses(signals?: readonly ('TERM' | 'KILL')[]): void; /** * Let the sandbox user reopen the permissions of its own files under the given path, for * harness-side traversal/cleanup of trees where a sandboxed process restricted permissions * (some tools chmod their outputs regardless of umask). */ export declare function relaxPermissionsAsSandboxUser(targetPath: string): void; /** * How long after a command's own deadline {@link startSandboxTimeoutWatchdog} force-kills the * sandbox user. Callers waiting for the watchdog to end a submission must wait at least this long. */ export declare const SANDBOX_WATCHDOG_GRACE_SECONDS = 5; /** Cancels a {@link startSandboxTimeoutWatchdog}; `fired` reports whether its deadline elapsed. */ export interface SandboxTimeoutWatchdog { cancel(): void; fired(): boolean; } /** * Start a harness-owned watchdog that force-kills every sandbox process after the given deadline. * A sandboxed submission can signal its own `timeout` supervisor (same UID), and a synchronous * spawn blocks the harness's event loop, so without this external deadline a submission could run * until the outer judge-server limit. The watchdog's `sh`/`sleep` run as the harness user, out of * the submission's reach; killing the watchdog's process group cancels it before it spawns sudo. * * ALWAYS cancel in a `finally`: the watchdog is detached and `unref`'d, so a leaked one keeps * running after the harness exits and would SIGKILL a later request's submission. */ export declare function startSandboxTimeoutWatchdog(timeoutSeconds: number): SandboxTimeoutWatchdog; /** * `fs.rm`-like removal with a fallback for trees holding sandbox-user-owned entries whose * permissions block deletion: let the sandbox user reopen its own files first, then retry. The * containing directory is relaxed as well — unlinking an entry needs write permission on its * parent, so a submission that chmods a directory it owns would otherwise make everything inside it * undeletable. */ export declare function forceRemove(targetPath: string): Promise;