プログラミング
ソフトウェアサンドボックスの基本
Software Sandboxing: The Basics (blog.emilua.org)
要約
この記事は、ソフトウェアサンドボックスの基本的な概念と実装方法について解説しています。サンドボックスとは、プログラムの権限を制限し、システムリソースへのアクセスを制御する環境のことです。Unixの伝統的な権限管理では不十分な場合に、より高度なOSインターフェースや、特権昇格を伴わない「裁量的な権限降下」といった手法が重要であることを説明しています。
全文翻訳
ソフトウェアサンドボックス:基本
// これらのポリシーはDockerのデフォルトプロファイルに大きく影響を受けています。さらにカスタマイズされています。
//
// - root権限を必要とするシステムコールを避ける。ここでのポリシーは主に特権のないユーザー(内部にrootを持つコンテナではない)が使用することを想定しています。システムコール自体は有害ではないかもしれませんが、より大きなBPFプログラムにつながり、結果としてオーバーヘッドが増加します。
// - デスクトップアプリケーションでのさらなるフィンガープリンティングに悪用される可能性のある、めったに使用されないシステムコールを避ける。このカテゴリには主にプロファイリングに役立つシステムコールが含まれます(例: mincore, cachestat)。
// - systemDのseccompフィルタセットとOpenBSDのpledgeプロミスに触発されたカテゴリに分割する。
POLICY Aio {
ALLOW {
io_cancel, io_destroy, io_getevents, io_pgetevents, io_setup, io_submit
}
}
POLICY BasicIo {
ALLOW {
read, readv, tee, vmsplice, write, writev,
// ioctl() は明らかに汎用/ストリーム/基本的なI/Oに関するものではありません。ioctl() は、デバイスドライバが何にでも使用できる、 disguisされたシステムコールです。しかし、ファイルI/O、ソケットI/O、TTY IOを行うプログラムは、glibcがいくつかの操作でioctl() を使用することに最終的に遭遇すると予想されるため、基本的なIOセットに含めて、他のIOカテゴリにも含めるように強制します。
ioctl
}
}
POLICY Clock {
ALLOW {
clock_getres, clock_gettime, gettimeofday, time, times
}
}
// 互換性のための小技。このポリシーファミリーは、別のリポジトリで保守するのに適した候補です。
POLICY CompatX86 {
ALLOW {
// 古い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
}
}
POLICY CompatDB32 {
ALLOW {
remap_file_pages
}
}
POLICY CompatSystemd {
ALLOW {
// SystemDはこれをマウントIDの取得に使用します
name_to_handle_at
}
}
POLICY CompatWine {
ALLOW {
modify_ldt
}
}
POLICY Credentials {
ALLOW {
getegid, geteuid, getgid, getgroups, getresgid, getresuid, getuid
}
}
POLICY CredentialsExtra {
ALLOW {
// SystemDは、任意のプロセスをクエリできるという理由で、このシステムコールを'process'ポリシーにリストしています。これはプロセス関係に関連するシステムコールです。同じ理由で、このカテゴリの他のシステムコールは任意のプロセスをクエリできないため、このシステムコールを'credentials'カテゴリに含めないことを選択します。しかし、capgetのほとんどの使用例ではcapgetはまったく必要なく、結果のBPFが大きくなるだけなので、'process'カテゴリにcapgetを含めないことも選択します。
capget
}
}
POLICY CredentialsMutation {
ALLOW {
capset, setfsgid, setfsuid, setgid, setgroups, setregid, setresgid,
setresuid, setreuid, setuid
}
}
// メモリ割り当て、スレッド化、システムコールインタラクション(またはlibcサポート)、および
// 常に利用可能であるべき関数(例: プログラムの最後の手段としての終了のためのexit_group)。
//
// libcベースのプログラムを実際に開くには、ローダーが必要なライブラリをファイルシステムからスキャンし、プログラムイメージを
// 結合するための多くの操作を行うため、はるかに多くのシステムコールへのアクセスが必要になることに注意してください。ここでの考えは、プログラムイメージがすでにRAMにある状態でCランタイムが実行を継続できるようにするフィルタを適用することです。
POLICY CRuntime {
ALLOW {
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の使用については、より良いメカニズムが存在します(例: 協力的なプロセス間でゼロコピーI/Oを行うためにmemfd+seal+mmapを使用できます)。
// - 過去にいくつかのCVEに登場しました。
POLICY Debug {
ALLOW {
kcmp, pidfd_getfd, perf_event_open, process_madvise, process_mrelease,
process_vm_readv, process_vm_writev, ptrace
}
}
POLICY FileDescriptors {
ALLOW {
close, close_range, dup, dup2, dup3, fcntl
}
}
// このポリシーはファイルシステムから分離されており、プロセスは引き続きファイルI/Oを実行できます。
//
// - すでに開いているファイル。
// - UNIXソケットから受信したファイル。
// - Memfds。
POLICY FileIo {
ALLOW {
copy_file_range, fadvise64, fallocate, flock, ftruncate, lseek, pread64,
preadv, preadv2, pwrite64, pwritev, pwritev2, readahead, sendfile,
splice
}
}
// OpenBSDのpledgeは、このプロミスをrpath, wpath, cpath, dpathにさらに分解しますが、Landlockはこの種の
// 詳細な設計の意図を反映するのに適しています。
POLICY Filesystem {
ALLOW {
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
}
}
// ファイルのstat構造体のフィールドに変更を加えることが許可されます。
POLICY FilesystemAttr {
ALLOW {
chmod, chown, fchmod, fchmodat, fchmodat2, fchown, fchownat,
fremovexattr, fsetxattr, futimesat, lchown, lremovexattr, lsetxattr,
removexattr, setxattr, utime, utimensat, utimes
}
}
// イベントループシステムコール。
POLICY IoEvent {
ALLOW {
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
POLICY IoUring {
ALLOW {
io_uring_enter, io_uring_register, io_uring_setup
}
}
// SysV IPC, POSIX Message Queues またはその他のIPC。
POLICY Ipc {
ALLOW {
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
}
}
// メモリロック制御。
POLICY Memlock {
ALLOW {
memfd_secret, mlock, mlock2, mlockall, munlock, munlockall
}
}
POLICY NetworkIo {
ALLOW {
connect, getpeername, getsockname, getsockopt, recvfrom, recvmmsg,
recvmsg, sendmmsg, sendmsg, sendto, setsockopt, shutdown
}
}
POLICY NetworkServer {
ALLOW {
accept, accept4, bind, listen
}
}
POLICY NetworkSocketTcp {
ALLOW {
socket(domain, type, protocol) {
(type & 0x7ff) == /*SOCK_STREAM=*/1 && protocol == 0 &&
(domain == /*AF_INET=*/2 || domain == /*AF_INET6=*/10)
}
}
}
POLICY NetworkSocketUdp {
ALLOW {
socket(domain, type, protocol) {
(type & 0x7ff) == /*SOCK_DGRAM=*/2 && protocol == 0 &&
(domain == /*AF_INET=*/2 || domain == /*AF_INET6=*/10)
}
}
}
POLICY NetworkSocketUnix {
ALLOW {
socket(domain, type, protocol) {
domain == /*AF_UNIX=*/1 && protocol == 0
},
socketpair(domain, type, protocol) {
domain == /*AF_UNIX=*/1 && protocol == 0
}
}
}
// メモリ保護キーに使用されるシステムコール。
POLICY Pkey {
ALLOW {
pkey_alloc, pkey_free, pkey_mprotect
}
}
// プロセス制御、実行、名前空間、関係操作。
//
// 他のバイナリをサンドボックス化するには、このセットへのアクセスがほぼ常に必要になります。
// <https://lore.kernel.org/all/202010281500.855B950FE@keescook/>。seccompフィルタからこのセットを除外するのは、
// 自身をサンドボックス化している場合(つまり、危険なことを行う前にさらに権限を協力的に降格させる場合)にのみ現実的です。
// Linuxにはseccompやcgroupsのようなexec時の遷移メカニズムがないのは残念です。SELinuxの担当者は、
// この種のメカニズムが権限を適切に降格するためにどれほど重要であるかをすでに知っています。
// そして、より多くのカーネルハッカーがこの教訓を学ぶことが望ましいです。
POLICY Process {
ALLOW {
// clone2はどこ? ia64はclone2を持つ唯一のアーキテクチャですが、ia64はseccompを実装していません。c.f.
// カーネル内の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
}
}
POLICY Resources {
ALLOW {
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
}
}
// リソース設定を変更する。
POLICY ResourcesMutation {
ALLOW {
ioprio_set, prlimit64, sched_setaffinity, sched_setattr, sched_setparam,
sched_setscheduler, setpriority, setrlimit
}
}
POLICY Sandbox {
ALLOW {
landlock_add_rule, landlock_create_ruleset, landlock_restrict_self,
seccomp
}
}
// プロセスシグナルハンドリング。
POLICY Signal {
ALLOW {
pause, rt_sigaction, rt_sigpending, rt_sigprocmask, rt_sigreturn,
rt_sigsuspend, rt_sigtimedwait, sigaltstack, signalfd, signalfd4
}
}
// ファイルとメモリをストレージに同期する。
POLICY Sync {
ALLOW {
fdatasync, fsync, msync, sync, sync_file_range, syncfs
}
}
// 時間による操作をスケジュールする。
POLICY Timer {
ALLOW {
alarm, getitimer, clock_nanosleep, nanosleep, setitimer, timer_create,
timer_delete, timer_getoverrun, timer_gettime, timer_settime,
timerfd_create, timerfd_gettime, timerfd_settime
}
}