Thiết lập môi trường sandbox mã nguồn an toàn với Docker: Từ Namespace đến gVisor

Khi xây dựng nền tảng cho phép người dùng thực thi mã tùy ý — như hệ thống OJ (Online Judge) hay IDE trực tuyến — việc cách ly mã độc là ưu tiên hàng đầu. Docker không phải "tường đồng vách sắt", mà chỉ là lớp vỏ bọc dựa trên kernel chung của host. Bài viết này trình bày cách triển khai sandbox mã nguồn an toàn bằng Docker thông qua nhiều lớp bảo vệ chồng lấn.

Cơ chế cô lập trong Docker: Hiểu đúng để phòng tránh

Docker sử dụng hai cơ chế chính:

  • Namespace: Tạo cảm giác "độc lập" cho tiến trình bên trong container (PID, Mount, Network, IPC, UTS, User).
  • Cgroup: Giới hạn tài nguyên (CPU, RAM, số lượng tiến trình...).

Tuy nhiên, đây là "cô lập mềm". Nếu có lỗi kernel hoặc cấu hình sai, mã độc có thể thoát ra host. Do đó, cần áp dụng mô hình defense in depth — nhiều lớp bảo vệ kết hợp.

Lớp 1: Giới hạn tài nguyên nghiêm ngặt

Ngăn fork bomb, vòng lặp vô hạn hoặc tiêu tốn tài nguyên:

docker run \
  --memory="512m" \
  --memory-swap="512m" \
  --cpus="1" \
  --pids-limit=50 \
  --cpu-shares=512 \
  your-image

Ghi chú:

  • --memory-swap="512m" vô hiệu hóa swap để tránh bypass giới hạn RAM.
  • --pids-limit=50 chặn các tấn công dạng :(){ :|:& };:.

Lớp 2: Loại bỏ Capability không cần thiết

Linux phân rã quyền root thành các capability nhỏ. Trong sandbox, gần như không cần capability nào:

docker run --cap-drop=ALL your-image

Nếu cần bind port dưới 1024, mới thêm --cap-add=NET_BIND_SERVICE. Tuyệt đối tránh --privileged.

Lớp 3: Lọc system call với Seccomp

Seccomp hoạt động như tường lửa ở cấp độ kernel. Cấu hình mặc định của Docker vẫn quá rộng cho sandbox. Nên dùng profile tùy chỉnh:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    { "names": ["read", "write", "open", "close", "execve", ...], "action": "SCMP_ACT_ALLOW" }
  ]
}

Khởi chạy container kèm profile:

docker run --security-opt seccomp=./strict.json your-image

Tip: Dùng strace để thu thập syscall cần thiết trước khi khóa cứng.

Lớp 4: Kiểm soát truy cập file với AppArmor

AppArmor giới hạn thao tác đọc/ghi file, mạng, mount... Ví dụ profile sandbox:

profile code-sandbox {
  /usr/** r,
  /lib/** r,
  /tmp/** rw,
  deny network,
  deny mount,
  deny ptrace,
}

Sử dụng:

docker run --security-opt apparmor=code-sandbox your-image

Lớp 5: Kích hoạt User Namespace

Map UID/GID trong container sang dải không đặc quyền trên host:

# /etc/docker/daemon.json
{ "userns-remap": "default" }

Sau khi khởi động lại Docker, UID 0 trong container sẽ tương ứng với UID ~100000 trên host — giảm thiểu hậu quả nếu xảy ra escape.

Lớp 6: Filesystem chỉ đọc + tmpfs

Không cho phép ghi vào hệ thống tập tin gốc:

docker run \
  --read-only \
  --tmpfs /tmp:rw,size=100m \
  --tmpfs /dev/shm:rw,size=64m \
  your-image

Mọi thay đổi chỉ tồn tại trong bộ nhớ và biến mất khi container dừng.

Lớp 7: Cô lập mạng hoàn toàn

Hầu hết sandbox không cần mạng:

docker run --network=none your-image

Nếu bắt buộc phải có mạng, hãy tạo bridge riêng và dùng iptables để whitelist địa chỉ đích.

Lớp 8: Giới hạn thời gian thực thi

Ngăn mã chạy vô hạn:

timeout 10 docker run --rm your-image

Hoặc kiểm soát timeout từ bên trong container bằng subprocess với tham số timeout.

Giải pháp nâng cao: gVisor hoặc Kata Containers

Với yêu cầu bảo mật cực cao (multi-tenant, mã hoàn toàn không tin cậy), nên dùng runtime sandbox:

  • gVisor: Cài đặt kernel giả (Sentry) ở userspace, chỉ forward ~20 syscall xuống host. Cài đặt đơn giản, overhead vừa phải.
  • Kata Containers: Mỗi container chạy trong VM nhẹ, có kernel riêng — cô lập mạnh hơn nhưng tốn tài nguyên.

Sử dụng gVisor với Docker:

docker run --runtime=runsc your-image

Cấu hình tổng hợp cho production

docker run \
  --rm \
  --runtime=runsc \
  --network=none \
  --read-only \
  --memory="512m" \
  --pids-limit=50 \
  --cap-drop=ALL \
  --security-opt seccomp=/etc/seccomp/strict.json \
  --security-opt apparmor=code-sandbox \
  --tmpfs /tmp:rw,size=100m \
  --user=1000:1000 \
  your-image \
  timeout 10 python3 /submission.py

Bài học thực tế

  • Không bao giờ mount /var/run/docker.sock vào container.
  • Luôn dùng image digest thay vì tag latest.
  • Cập nhật kernel thường xuyên — nhiều CVE escape xuất phát từ lỗi kernel.
  • Giám sát hành vi bất thường bằng Falco hoặc auditd.

Thẻ: docker gVisor seccomp AppArmor container-security

Đăng vào ngày 24 tháng 9 lúc 20:30