软件沙盒技术:基础知识(2025)
摘要
本文解释了软件沙盒技术的基础知识,详细介绍了通过使用 seccomp 和 BPF 等技术来增强安全性的系统调用策略,并受到了 Docker、systemd 和 OpenBSD 的影响。
<p><a href="https://lobste.rs/s/brptxb/software_sandboxing_basics_2025">评论</a></p>
查看缓存全文
缓存时间: 2026/09/20 16:14
# 软件沙箱:基础知识
来源:https://blog.emilua.org/2025/01/12/software-sandboxing-basics/
```
// 这些策略在很大程度上受到 Docker 默认配置文件的启发。在此基础上进行了进一步的自定义:
//
// - 避免需要 root 权限的系统调用。此处的策略主要面向非特权用户(而非具有 root 权限的容器)使用。
// 这些系统调用本身无害,但会导致生成更大的 BPF 程序,从而增加更多开销。
// - 避免极少使用且可能被滥用于对桌面应用程序进行进一步指纹识别的系统调用。
// 此类别主要包含用于性能分析的系统调用(例如 mincore、cachestat)。
// - 受 systemd 的 seccomp 过滤集和 OpenBSD 的 pledge 承诺启发,将它们划分为不同的类别。
策略 Aio {
允许 { io_cancel, io_destroy, io_getevents, io_pgetevents, io_setup, io_submit }
}
策略 BasicIo {
允许 { read, readv, tee, vmsplice, write, writev,
// ioctl() 绝对不是用于通用/流/基本 I/O 的。ioctl() 实际上是一个伪装的系统调用,
// 设备驱动程序可以将其用于任何目的。然而,任何执行文件 I/O 或
// 套接字 I/O 或 TTY I/O 的程序最终都可能会遇到 glibc 为了某些操作使用 ioctl(),
// 所以我们直接将其包含在基本 I/O 集中,以强制其他 I/O 类别也包含它。
ioctl
}
}
策略 Clock {
允许 { clock_getres, clock_gettime, gettimeofday, time, times }
}
// 兼容性变通方法。这个策略系列是放在不同仓库中维护的良好候选。
策略 CompatX86 {
允许 {
// 对于旧 ABI 仿真很重要
personality(persona) {
persona == /*PER_LINUX=*/0 || persona == /*PER_LINUX32=*/8 ||
persona == /*UNAME26=*/0x0020000 || persona == /*PER_LINUX32|UNAME26=*/0x20008 ||
persona == 0xffffffff
},
// 对于 x86 家族的 ABI 很重要。我们将其放在这里而不是 c-runtime,
// 因为其他架构不需要它。理想情况下,Kafel 应该允许我们在 c-runtime 中编写
// arch_prctl@amd64,并且该规则仅在我们为 amd64 架构构建时才包含。
arch_prctl
}
}
策略 CompatDB32 {
允许 { remap_file_pages }
}
策略 CompatSystemd {
允许 {
// systemd 使用此调用获取挂载 ID
name_to_handle_at
}
}
策略 CompatWine {
允许 { modify_ldt }
}
策略 Credentials {
允许 { getegid, geteuid, getgid, getgroups, getresgid, getresuid, getuid }
}
策略 CredentialsExtra {
允许 {
// systemd 在策略 'process' 中列出此系统调用,理由是它能够查询任意进程,
// 因此它是一个与进程关系相关的系统调用。遵循相同的逻辑,
// 我们选择不将此系统调用包含在策略 'credentials' 中,
// 因为该类别中的其他系统调用不允许查询任意进程。
// 然而,我们也选择不将 capget 包含在 'process' 类别中,
// 因为该策略的大多数使用根本不需要 capget,
// 只会使最终的 BPF 变大。
capget
}
}
策略 CredentialsMutation {
允许 { capset, setfsgid, setfsuid, setgid, setgroups, setregid, setresgid, setresuid, setreuid, setuid }
}
// 内存分配、线程、系统调用交互(或 libc 支持)以及应始终可用的函数
// (例如 exit_group 作为程序最后的退出手段)。
//
// 请注意,实际上加载基于 libc 的程序需要访问更多的系统调用,
// 因为加载器需要扫描文件系统以获取所需的库,并执行许多操作来
// 拼接程序镜像。这里的想法是应用一个过滤器,
// 允许 C 运行时在程序镜像已加载到 RAM 后继续运行。
策略 CRuntime {
允许 { brk, exit, exit_group, futex, futex_requeue, futex_wait, futex_waitv, futex_wake,
get_robust_list, get_thread_area, gettid, madvise, map_shadow_stack,
membarrier, mmap, mprotect, mremap, munmap, restart_syscall, rseq,
sched_yield, set_robust_list, set_thread_area, set_tid_address,
// glibc 的 malloc() 引用了 getrandom(),因此包含在此处
getrandom
}
}
// 这些系统调用已受 YAMA 的 ptrace_scope 或能力(例如 CAP_PERFMON)的限制。
// 通常的推理是允许它们是安全的,但是:
//
// - 它们确实只对进程检查/调试有用。
// - 对于 IPC 用途,有更好的机制(例如,可以使用 memfd+secal+mmap
// 在协作进程之间实现零拷贝 I/O)。
// - 它们过去曾出现在几个 CVE 中。
策略 Debug {
允许 { kcmp, pidfd_getfd, perf_event_open, process_madvise, process_mrelease,
process_vm_readv, process_vm_writev, ptrace
}
}
策略 FileDescriptors {
允许 { close, close_range, dup, dup2, dup3, fcntl }
}
// 此策略从文件系统策略中分离出来,以便进程仍可对以下内容执行文件 I/O:
//
// - 已打开的文件。
// - 从 UNIX 套接字接收的文件。
// - memfd。
策略 FileIo {
允许 { copy_file_range, fadvise64, fallocate, flock, ftruncate, lseek, pread64,
preadv, preadv2, pwrite64, pwritev, pwritev2, readahead, sendfile, splice
}
}
// OpenBSD 的 pledge 进一步将此承诺细分为 rpath、wpath、cpath 和 dpath,
// 但使用 Landlock 来反映这种细粒度设计的意图将更为合适
策略 Filesystem {
允许 { access, chdir, creat, faccessat, faccessat2, fchdir, fgetxattr, flistxattr,
fstat, fstatfs, getcwd, getdents, getdents64, getxattr, inotify_add_watch,
inotify_init, inotify_init1, inotify_rm_watch, lgetxattr, link, linkat,
listxattr, llistxattr, lstat, mkdir, mkdirat, mknod, mknodat, newfstatat,
open, openat, openat2, readlink, readlinkat, rename, renameat, renameat2,
rmdir, stat, statfs, statx, symlink, symlinkat, truncate, umask, unlink, unlinkat
}
}
// 允许明确更改与文件相关的 struct stat 字段。
策略 FilesystemAttr {
允许 { chmod, chown, fchmod, fchmodat, fchmodat2, fchown, fchownat, fremovexattr,
fsetxattr, futimesat, lchown, lremovexattr, lsetxattr, removexattr, setxattr,
utime, utimensat, utimes
}
}
// 事件循环系统调用。
策略 IoEvent {
允许 { epoll_create, epoll_create1, epoll_ctl, epoll_ctl_old, epoll_pwait,
epoll_pwait2, epoll_wait, epoll_wait_old, eventfd, eventfd2, poll, ppoll,
pselect6, select
}
}
// io_uring 如今通常被认为是不安全的:
// http://security.googleblog.com/2023/06/learnings-from-kctf-vrps-42-linux.html
策略 IoUring {
允许 { io_uring_enter, io_uring_register, io_uring_setup }
}
// System V IPC、POSIX 消息队列或其他 IPC。
策略 Ipc {
允许 { memfd_create, mq_getsetattr, mq_notify, mq_open, mq_timedreceive,
mq_timedsend, mq_unlink, msgctl, msgget, msgrcv, msgsnd, pipe, pipe2,
semctl, semget, semop, semtimedop, shmat, shmctl, shmdt, shmget
}
}
// 内存锁定控制。
策略 Memlock {
允许 { memfd_secret, mlock, mlock2, mlockall, munlock, munlockall }
}
策略 NetworkIo {
允许 { connect, getpeername, getsockname, getsockopt, recvfrom, recvmmsg,
recvmsg, sendmmsg, sendmsg, sendto, setsockopt, shutdown
}
}
策略 NetworkServer {
允许 { accept, accept4, bind, listen }
}
策略 NetworkSocketTcp {
允许 { socket(domain, type, protocol) {
(type & 0x7ff) == /*SOCK_STREAM=*/1 && protocol == 0 &&
(domain == /*AF_INET=*/2 || domain == /*AF_INET6=*/10)
} }
}
策略 NetworkSocketUdp {
允许 { socket(domain, type, protocol) {
(type & 0x7ff) == /*SOCK_DGRAM=*/2 && protocol == 0 &&
(domain == /*AF_INET=*/2 || domain == /*AF_INET6=*/10)
} }
}
策略 NetworkSocketUnix {
允许 { socket(domain, type, protocol) { domain == /*AF_UNIX=*/1 && protocol == 0 },
socketpair(domain, type, protocol) { domain == /*AF_UNIX=*/1 && protocol == 0 }
}
}
// 用于内存保护密钥的系统调用。
策略 Pkey {
允许 { pkey_alloc, pkey_free, pkey_mprotect }
}
// 进程控制、执行、命名空间、关系操作。
//
// 最有可能的是,您将始终需要访问此集合来对其他二进制文件进行沙箱化:
// https://docs.kernel.org/security/seccomp.html#known-issues
// 只有当您沙箱化自己时(即在执行危险操作之前协作地放弃更多权限),
// 才真正有实际意义将此集合从 seccomp 过滤器中排除。
// 很遗憾 Linux 没有为 seccomp 或 cgroups 提供这种
// 执行时转换机制。SELinux 的人们已经知道支持这种机制对于
// 正确放弃权限有多么重要,让更多的内核黑客也学到这一点是件好事。
策略 Process {
允许 {
// clone2 在哪里?ia64 是唯一拥有 clone2 的架构,但
// ia64 没有实现 seccomp。请参阅内核中的
// acce2f71779c54086962fefce3833d886c655f62。
clone, clone3, execve, execveat, fork, getpgid, getpgrp, getpid, getppid,
getrusage, getsid, kill, pidfd_open, pidfd_send_signal, prctl,
rt_sigqueueinfo, rt_tgsigqueueinfo, setpgid, setsid, tgkill, tkill,
vfork, wait4, waitid
}
}
策略 Resources {
允许 { getcpu, getpriority, getrlimit, ioprio_get, sched_getaffinity,
sched_getattr, sched_getparam, sched_get_priority_max, sched_get_priority_min,
sched_getscheduler, sched_rr_get_interval
}
}
// 更改资源设置。
策略 ResourcesMutation {
允许 { ioprio_set, prlimit64, sched_setaffinity, sched_setattr, sched_setparam,
sched_setscheduler, setpriority, setrlimit
}
}
策略 Sandbox {
允许 { landlock_add_rule, landlock_create_ruleset, landlock_restrict_self, seccomp }
}
// 进程信号处理。
策略 Signal {
允许 { pause, rt_sigaction, rt_sigpending, rt_sigprocmask, rt_sigreturn,
rt_sigsuspend, rt_sigtimedwait, sigaltstack, signalfd, signalfd4
}
}
// 将文件和内存同步到存储。
策略 Sync {
允许 { fdatasync, fsync, msync, sync, sync_file_range, syncfs }
}
// 按时间调度操作。
策略 Timer {
允许 { alarm, getitimer, clock_nanosleep, nanosleep, setitimer, timer_create,
timer_delete, timer_getoverrun, timer_gettime, timer_settime,
timerfd_create, timerfd_gettime, timerfd_settime
}
}
```
相似文章
Linux 应用沙箱——旧技术的新未来
文章推荐使用成熟的 Firejail 工具,在 Linux 上限制程序的网络、文件系统及硬件访问,无需等待 Wayland 等新显示技术。
沙盒化令人抓狂
一篇技术博客,讨论实现安全沙盒技术的复杂性与挫败感。
使用 Bubblewrap 在 Linux 上轻松实现沙箱
一篇博客文章,介绍了一种在 Linux 上使用 Bubblewrap 的轻量级沙箱方法,其中名为 'box' 的脚本以只读方式共享主机文件系统,并以可读写方式共享当前目录,从而无需单独发行版即可在隔离环境中运行主机二进制文件。
你遇到的最糟的沙箱失败案例是什么?
一位开发者质疑IDE中对LLM命令沙箱化的充分性,并询问社区关于安全失败的经验。
CreateOS Sandbox
CreateOS Sandbox 为AI代理提供即时、硬件隔离的沙盒环境。