Service: hacknow-cp-compiler v2.2
Last updated: 2026-05-09
This service compiles and executes arbitrary user code for competitive programming judging. It accepts source code via authenticated API, runs it inside an nsjail sandbox, and returns stdout/stderr/resource usage.
In scope: preventing sandboxed code from reading host files, exfiltrating data over the network, exhausting host resources, or escaping to the container/host.
Out of scope: application-layer DoS against the API itself (rate limiting is the caller's responsibility), supply-chain attacks on compiler toolchains, and vulnerabilities in the host kernel.
Threat: Sandboxed process uses a privileged syscall (e.g., bpf, userfaultfd, io_uring) to escape nsjail namespaces.
Mitigation: nsjail runs with --mode o (one-shot), user/mount/PID/net namespaces, --disable_proc, --iface_no_lo. Docker seccomp profile blocks bpf, userfaultfd, io_uring_*, kexec_load, keyctl. Container runs no-new-privileges:true and drops NET_ADMIN, MKNOD, AUDIT_WRITE.
Residual risk: Kernel zero-day in an allowed syscall. Mitigate by keeping the host kernel patched.
Threat: Code reads /etc/shadow, env files, or INTERNAL_TOKEN via #include, include_bytes!, env!(), or //go:embed.
Mitigation: (1) Bindmounts are selective — only linker cache, SSL certs, locale, language toolchains are mounted read-only. Full /etc is NOT mounted. (2) Source-level validation blocks absolute-path includes, path traversal (..), Go embed directives, and Rust env!/option_env! macros before compilation starts. (3) nsjail maps user to UID 1000 with no capabilities.
Residual risk: A new language's compiler may read files via mechanisms not yet covered by source validation.
Threat: Sandboxed code opens a socket to send stolen data or download payloads.
Mitigation: nsjail --iface_no_lo disables all network interfaces inside the sandbox — no loopback, no external connectivity. The container itself binds only to 127.0.0.1:8000.
Residual risk: None under normal operation. If nsjail's network namespace is bypassed, Docker's NET_ADMIN cap_drop limits further abuse.
Threat: Code forks aggressively, allocates unbounded memory, or writes large files to exhaust host resources.
Mitigation: nsjail enforces --rlimit_nproc 512, --rlimit_as (4x requested, capped), --rlimit_fsize (16 MB), --max_cpus 1, wall-clock + CPU time limits. /tmp inside sandbox is a 32 MB tmpfs. The compiler temp dir is a 512 MB tmpfs at the container level. Docker resource limits cap the container at 2 GB RAM and 2 CPUs. Concurrency semaphores (global + per-language) prevent oversubscription.
Residual risk: A slow leak within limits could degrade performance over hours. Health check + restart policy handles this.
Threat: Attacker calls the API without a valid token, or extracts INTERNAL_TOKEN from inside the sandbox.
Mitigation: Bearer token auth with constant-time comparison (secrets.compare_digest). Tokens shorter than 32 chars or with low entropy are rejected at startup. The token is passed via .env (not baked into the image). Inside the sandbox, env vars are explicitly set — INTERNAL_TOKEN is never forwarded. Source-level Rust env!() blocking prevents compile-time env exfiltration.
Residual risk: If .env file permissions are too open on the host, local users could read it.
Threat: Malicious code exploits compiler bugs (e.g., GCC LTO bugs, #pragma abuse) to write outside the sandbox during compilation.
Mitigation: Compilation also runs inside nsjail with the same namespace isolation. C/C++ gets -ffile-prefix-map=/sandbox=. and depth limits. Compile timeout prevents resource exhaustion during compilation.
Residual risk: Compiler zero-days. Keep toolchains updated.
User code
│
├─ Source validation (regex: blocks #include "/../", env!(), //go:embed)
│
├─ nsjail (Layer 1)
│ ├─ User/PID/mount/net namespaces
│ ├─ UID 1000, no capabilities
│ ├─ No network interfaces
│ ├─ Read-only selective bindmounts
│ ├─ rlimits: CPU, memory, fsize, nproc
│ └─ /proc disabled (--disable_proc)
│
├─ Docker container (Layer 2)
│ ├─ seccomp profile (blocks bpf, io_uring, kexec, keyctl, userfaultfd)
│ ├─ CAPs: only SYS_ADMIN, SYS_PTRACE, SYS_CHROOT, SETUID, SETGID
│ ├─ no-new-privileges
│ ├─ 2 GB memory / 2 CPU limit
│ └─ tmpfs for compiler workspace
│
└─ Host kernel (Layer 3)
└─ Standard kernel isolation; keep patched
-
Host PID visibility:
/procis bind-mounted read-only for JVM/Mono/rustc/GHC compatibility. Sandboxed code can enumerate host PIDs via/proc. This leaks process names but not memory.--disable_procprevents new procfs mounts but the bind-mount is still visible. -
Timing side channels: Execution time is measured and returned to the caller. Code can measure its own wall-clock time. No mitigation — inherent to a judge system.
-
Compiler information leakage: Error messages from compilers may reveal absolute paths, installed package versions, or kernel version strings.
-ffile-prefix-mapreduces this for C/C++ but not all languages. -
Nsjail seccomp: applied via
--seccomp_stringto blockmount,pivot_root,unshare,bpf,kexec_load,keyctl,userfaultfd,io_uring_*, andptracefamily inside the sandbox. Layered on top of the Docker-level seccomp profile. -
AppArmor disabled:
apparmor:unconfinedis set because nsjail's mount namespace operations conflict with default AppArmor profiles. A custom AppArmor profile would improve defense-in-depth. -
Shared kernel: All sandboxes share the host kernel. A kernel exploit in any sandbox compromises everything. gVisor or Firecracker would provide stronger isolation but add latency.
If you find a sandbox escape or authentication bypass:
- Do not open a public issue.
- Email hacknow.uz@gmail.com with reproduction steps.
- We aim to acknowledge within 48 hours and patch within 7 days for critical issues.
-
INTERNAL_TOKENis a cryptographically random string >= 32 characters -
.envfile is chmod 600, owned by root or the deploy user - Docker socket is not mounted into the container
- Host kernel is >= 5.15 with user namespace support
-
docker-compose.ymluses the seccomp profile (seccomp:./docker/seccomp-profile.json) - Container image is rebuilt periodically to pick up compiler security patches
- API is behind a reverse proxy with rate limiting (not handled by this service)
-
/compiler-temptmpfs is sized appropriately for expected concurrency - Log level is
WARNINGor above in production (no debug logging of user code) - Health check endpoint (
/api/v1/health) is monitored - Container restart policy is
unless-stopped(handles OOM kills) - Network: container port bound to
127.0.0.1, not0.0.0.0