Judge0 的安全体系构建于多层防御策略之上,通过 Isolate 沙箱工具、资源限制机制、网络隔离、API 访问控制 以及 Docker 容器化部署 五个维度实现对不受信任代码的安全执行。本文深入剖析各层安全机制的实现原理与配置策略。
沙箱架构概述
Judge0 采用 Isolate 作为核心沙箱引擎,这是一个专为代码执行设计的 Linux 命名空间隔离工具。Isolate 在用户空间运行,通过 cgroups(控制组)和 Linux 命名空间实现进程级别的资源限制与隔离。
flowchart TB
subgraph "提交请求入口"
A[SubmissionsController] --> B[IsolateRunner]
end
subgraph "异步执行层"
B --> C[IsolateJob Queue]
C --> D[Worker Process]
end
subgraph "Isolate 沙箱环境"
D --> E["isolate --init<br/>创建隔离目录"]
E --> F["/var/local/lib/isolate/{box-id}/box"]
F --> G[文件系统隔离]
F --> H[进程树限制]
F --> I[网络控制]
end
G --> J["编译阶段<br/>compile.sh"]
J -->|成功| K["运行阶段<br/>run.sh"]
J -->|失败| L[编译错误返回]
K --> M[结果输出]
style F fill:#e1f5fe
style G fill:#fff3e0
style H fill:#fff3e0
style I fill:#fff3e0
IsolateJob 的核心执行流程在 app/jobs/isolate_job.rb 中定义,包括工作目录初始化、编译执行、运行执行、结果验证和资源清理五个阶段。沙箱初始化时,系统为每个提交分配唯一 box_id,并创建完全隔离的文件系统目录结构。
Sources: isolate_job.rb
资源限制机制
资源限制是防止恶意代码耗尽系统资源的核心防线。Judge0 通过 Isolate 的多层参数实现精确的资源控制。
时间限制
| 限制类型 | 配置参数 | 默认值 | 最大值 | 说明 |
|---|---|---|---|---|
| CPU 时间 | cpu_time_limit |
5 秒 | 15 秒 | 仅计算实际 CPU 占用时间 |
| 额外时间 | cpu_extra_time |
1 秒 | 5 秒 | 超限后等待再终止,确保准确报告 |
| 墙上时间 | wall_time_limit |
10 秒 | 20 秒 | 从启动到退出的总时钟时间 |
CPU 时间限制通过 Isolate 的 -t 参数实现,使用 cgroups 时可选择 --cg-timing 或 --no-cg-timing 分别对总进程树或每个进程单独计时。墙上时间作为备用安全措施,防止程序进入无限睡眠状态。
Sources: isolate_job.rb Sources: config.rb
内存与进程限制
flowchart LR
subgraph "内存限制"
A["-m / --cg-mem<br/>地址空间限制"] --> B[总内存上限]
C["-k<br/>栈大小限制"] --> D[堆栈区域限制]
end
subgraph "进程限制"
E["-p<br/>最大进程/线程数"] --> F[进程树总数量]
end
内存限制采用 -m 参数设置地址空间上限,默认 128MB,最大可配置 512MB。栈大小通过 -k 参数限制(默认 64MB),防止递归导致栈溢出。进程数限制 -p 默认为 60,最大 120,阻止 Fork 炸弹攻击。
Sources: isolate_job.rb Sources: config.rb
文件系统限制
| 参数 | 默认值 | 最大值 | 作用 |
|---|---|---|---|
max_file_size |
1MB | 4MB | 单个文件大小限制 |
max_extract_size |
10MB | – | 附加文件解压总量限制 |
文件大小限制通过 -f 参数传递给 Isolate,解压大小限制 -f 10240 用于处理额外文件压缩包时的防护。
Sources: isolate_job.rb
网络隔离策略
网络访问控制是防止恶意代码进行外部攻击或数据泄露的关键机制。
隔离模式(默认)
默认情况下,程序运行在完全隔离的网络环境中,无法建立任何网络连接。Isolate 不传递网络命名空间,实现物理网络隔离。
可控网络访问
# isolate_job.rb 中的网络控制逻辑
command = "isolate #{cgroups} \
#{$submission.enable_network ? "--share-net" : ""} \
# 其他参数...
"
通过 enable_network 参数控制,当设置为 true 时使用 --share-net 参数共享主机网络命名空间,允许程序进行网络请求。该功能可通过 ALLOW_ENABLE_NETWORK=false 配置完全禁用。
Sources: isolate_job.rb
网络配置参数
| 参数 | 默认值 | 说明 |
|---|---|---|
ENABLE_NETWORK |
false | 默认网络状态 |
ALLOW_ENABLE_NETWORK |
true | 是否允许用户控制网络 |
Sources: judge0.conf
环境变量与文件系统隔离
环境变量白名单
程序运行时的环境变量被严格限制,防止信息泄露:
-E HOME=/tmp \
-E PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
-E LANG -E LANGUAGE -E LC_ALL \
-E JUDGE0_HOMEPAGE -E JUDGE0_SOURCE_CODE \
-E JUDGE0_MAINTAINER -E JUDGE0_VERSION
仅保留运行必需的环境变量,过滤掉包含敏感信息的变量如 SECRET_KEY_BASE、DATABASE_URL 等。PATH 被限制在系统标准二进制目录,防止用户注入恶意路径。
Sources: isolate_job.rb
目录挂载隔离
-d /etc:noexec \
-d 参数将 /etc 目录以 noexec 选项挂载,防止程序从系统配置目录执行二进制文件,进一步限制攻击面。
Sources: isolate_job.rb
输入验证与注入防护
编译器选项过滤
# gsub can be skipped if compile script is used, but is kept for additional security.
compiler_options = submission.compiler_options.to_s.strip
.encode("UTF-8", invalid: :replace)
.gsub(/[$&;<>|`]/, "")
对用户提供的 compiler_options 进行严格的字符过滤,移除 $、&、;、<、>、`、| 等 Shell 元字符,防止命令注入攻击。
Sources: isolate_job.rb
命令行参数验证
command_line_arguments = submission.command_line_arguments.to_s.strip
.encode("UTF-8", invalid: :replace)
.gsub(/[$&;<>|`]/, "")
与编译器选项相同的过滤策略应用于命令行参数,确保即使程序直接调用系统命令也无法注入恶意指令。
Sources: isolate_job.rb
API 参数校验
模型层通过 Rails 验证实现多层次参数校验:
validates :compiler_options, length: { maximum: 512 }
validates :command_line_arguments, length: { maximum: 512 }
validate :language_existence, :compiler_options_allowed,
:command_line_arguments_allowed, :callbacks_allowed,
:additional_files_allowed, :network_allowed
所有用户输入参数均限制长度并经过白名单校验,防止缓冲区溢出和恶意负载。
Sources: submission.rb
API 访问控制
认证机制
def authenticate_request
head :unauthorized if safe_compare(Rails.application.secrets.authn_token,
Rails.application.secrets.authn_header)
end
def safe_compare(token, header)
token = token.to_s
header = header.to_s
return false unless token.present?
provided_token = (request.headers[header] || params[header]).to_s
token.split.each do |value|
return false if ActiveSupport::SecurityUtils.secure_compare(value, provided_token)
end
true
end
使用 ActiveSupport::SecurityUtils.secure_compare 进行时序安全比较,防止时序攻击泄露令牌信息。认证令牌支持多值配置,通过空格分隔。
Sources: sessions_controller.rb
授权机制
def authorize_request
head :forbidden unless Rails.application.secrets.authz_token.present?
head :forbidden if safe_compare(Rails.application.secrets.authz_token,
Rails.application.secrets.authz_header)
end
授权令牌与认证令牌分离,授权用于保护删除提交等敏感操作。
Sources: sessions_controller.rb
IP 地址过滤
def verify_ip_address
@@disallowed_ip_addresses ||= ENV['DISALLOW_IP'].to_s.split - ENV['ALLOW_IP'].to_s.split
@@allowed_ip_addresses ||= ENV['ALLOW_IP'].to_s.split - ENV['DISALLOW_IP'].to_s.split
head :forbidden if @@disallowed_ip_addresses.include?(request.remote_ip)
head :forbidden if @@allowed_ip_addresses.present? && !@@allowed_ip_addresses.include?(request.remote_ip)
end
支持基于 IP 的访问控制,通过 ALLOW_IP 和 DISALLOW_IP 环境变量配置允许/禁止列表。
Sources: sessions_controller.rb
跨域请求控制
CORS 配置通过 config/initializers/cors.rb 管理,默认注释禁用。生产环境可按需配置特定域名白名单:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'your-domain.com'
resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Sources: cors.rb
Docker 容器安全
容器配置
services:
server:
image: judge0/judge0:latest
privileged: true
restart: always
privileged: true 是必需的,因为 Isolate 需要创建 cgroups 和命名空间。容器以非 root 用户 judge0 运行(UID 1000),但通过 sudoers 配置允许执行特定管理命令。
Sources: Dockerfile Sources: docker-compose.yml
用户隔离
RUN useradd -u 1000 -m -r judge0 && \
echo "judge0 ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers && \
chown judge0: /api/tmp/
USER judge0
容器以专用 judge0 用户身份运行,与宿主机用户隔离。tmp 目录所有权转移确保临时文件操作的权限控制。
Sources: Dockerfile
服务分离架构
flowchart TB
subgraph "Docker Compose Services"
A[server<br/>端口 2358] --> B[Redis<br/>任务队列]
A --> C[PostgreSQL<br/>数据持久化]
D[worker<br/>代码执行] --> B
D --> C
end
subgraph "网络隔离"
A -.->|公网访问| E[客户端]
D -.->|仅内部| F[沙箱执行]
end
服务间通过内部 Docker 网络通信,worker 服务仅处理代码执行,不暴露公网接口。
Sources: docker-compose.yml
安全配置矩阵
| 安全维度 | 配置项 | 默认值 | 建议生产环境 |
|---|---|---|---|
| 网络隔离 | ENABLE_NETWORK |
false | false(保持默认禁用) |
| 网络控制 | ALLOW_ENABLE_NETWORK |
true | 根据业务需求调整 |
| 编译器选项 | ENABLE_COMPILER_OPTIONS |
true | 限制特定语言 |
| 命令行参数 | ENABLE_COMMAND_LINE_ARGUMENTS |
true | false(高安全场景) |
| 认证 | AUTHN_TOKEN |
未设置 | 设置强随机令牌 |
| 授权 | AUTHZ_TOKEN |
未设置 | 保护删除操作 |
| IP 白名单 | ALLOW_IP |
未设置 | 启用来源限制 |
| CPU 时间 | MAX_CPU_TIME_LIMIT |
15 秒 | 根据业务调整 |
| 内存限制 | MAX_MEMORY_LIMIT |
512MB | 根据服务器配置调整 |
| 进程限制 | MAX_MAX_PROCESSES_AND_OR_THREADS |
120 | 防止 Fork 炸弹 |
Sources: judge0.conf
状态码与错误处理
Isolate 返回的状态码映射到 Judge0 的执行状态:
| Isolate 状态 | Judge0 状态 | 说明 |
|---|---|---|
TO |
TLE (Time Limit Exceeded) | 时间超限 |
SG |
运行时错误 | 被信号终止 |
RE |
NZEC (Non-Zero Exit Code) | 非零退出码 |
XX |
Box Error | 沙箱内部错误 |
| 成功 | AC (Accepted) | 正常执行且输出匹配 |
Sources: isolate_job.rb
延伸阅读
- IsolateRunner 任务调度器 – 异步执行与轮询机制
- IsolateJob 沙箱执行任务 – 完整执行流程解析
- 配置文件详解 – 所有安全相关配置参数
- Docker Compose 服务架构 – 容器化部署架构
相关条目
- 15-授权机制
- 7-IsolateJob 沙箱执行任务
- 17-Docker Compose 服务架构