syscall: support process sandboxing using Landlock on Linux
要約
概要
Linux カーネルの Landlock LSM(Linux Security Module)を用いて、Goプログラムから起動するサブプロセスのみをサンドボックス化できるよう、syscall.SysProcAttr 構造体に新しいフィールドを追加するproposalです。呼び出し元のGoプロセス自身には影響を与えず、子プロセスのみにアクセス制御ポリシーを適用できるようになります。
ステータス変更
(なし) → active
2026年5月27日のProposal Review Meeting(@adonovan, @bradfitz, @cherrymui, @griesemer, @ianlancetaylor, @neild, @rolandshoemaker 参加)にて、本proposalがactiveカラムに追加され、週次ミーティングでの継続審議対象となりました。@rsc によるAPI設計の具体的なフィードバック(フィールド名の統一、NoNewPrivs の独立化)があり、実装CLも提出済みであることから、正式なproposalプロセスへの移行が判断されたものと考えられます。なお、@aclements と @rsc が「より汎用的な fork/exec 間syscall指定機構」の可能性も議論しており、この点も引き続き検討中です。
技術的背景
現状の問題点
Landlock LSM はLinux上でプロセスのアクセス権限を制限できる非特権サンドボックス機構であり、多くのLinuxディストリビューションでデフォルト有効です。しかし、GoはGoランタイムのマルチスレッド性質上 fork(2) を直接安全に使えないため、fork-exec の間に処理を挟む SysProcAttr 経由でのみ子プロセスに特殊なOS設定を適用できます。
現状では landlock_restrict_self(2) を呼ぶと呼び出したプロセス自身が制限されてしまいます。つまり、子プロセスだけをサンドボックス化したい場合、親プロセスごとサンドボックス化するか、二重forkという回避策(ライブラリコードでは不可能)を使うしかありません。
提案された解決策
syscall.SysProcAttr(Linux限定)に以下の4つのフィールドを追加します:
type SysProcAttr struct {
// ... 既存フィールド ...
// Landlock LSM によるサンドボックス化
NoNewPrivs bool // prctl(PR_SET_NO_NEW_PRIVS) を fork 後・exec 前に呼ぶ
UseLandlock bool // landlock_restrict_self(LandlockFD, LandlockFlags) を呼ぶ
LandlockFD int // landlock_restrict_self の ruleset_fd 引数
LandlockFlags uintptr // landlock_restrict_self の flags 引数
}
内部実装では forkAndExecInChild1() 関数内(fork後・exec前の特殊な実行環境)で RawSyscall() を用いてシステムコールを発行します。PR_SET_NO_NEW_PRIVS と SYS_LANDLOCK_RESTRICT_SELF の定数は syscall パッケージ内に非公開定数として手動定義されます(internal/syscall/unix を使うとインポートループが発生するため)。
これによって何ができるようになるか
Landlock のルールセット(ファイルアクセスポリシー)を事前に親プロセス側で構築しておき、そのファイルディスクリプタをサブプロセスの SysProcAttr に渡すだけで、親プロセスには一切影響を与えずに子プロセスのファイルアクセスを制限できます。
主なユースケース:
- 信頼できない外部コマンド(
git,ffmpegなど)を特定ディレクトリのみにアクセス制限して実行 - Webサーバーが外部プロセスを起動する際のセキュリティ強化
github.com/landlock-lsm/go-landlockなどのライブラリと組み合わせた、宣言的なサンドボックスポリシーの適用
コード例
// Before: 現状のワークアラウンド(二重fork や 親プロセスごとサンドボックス化が必要)
// → ライブラリコードでは実質不可能、または親プロセスも制限されてしまう
// After: 新フィールドを使ったサブプロセス専用サンドボックス化
import (
"os/exec"
"syscall"
"github.com/landlock-lsm/go-landlock/landlock"
)
// 1. 親プロセス側でLandlockルールセットを構築(go-landlockライブラリを使用)
// ※ AsRuleset() は現在未実装だが、こういった形で提供される想定
rulesetFD, flags, err := landlock.V8.BestEffort().AsRuleset().Restrict(
landlock.RODirs("/"), // ルートディレクトリは読み取り専用
landlock.RWDirs("/tmp"), // /tmp のみ読み書き可能
)
if err != nil {
log.Fatal(err)
}
defer syscall.Close(rulesetFD)
// 2. サブプロセスにのみLandlockポリシーを適用して実行
cmd := exec.Command("git", "clone", untrustedURL, tmpDir)
cmd.SysProcAttr = &syscall.SysProcAttr{
NoNewPrivs: true, // Landlock適用の前提条件
UseLandlock: true,
LandlockFD: rulesetFD,
LandlockFlags: flags,
}
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
// 親プロセスのファイルアクセス権限は変化しない
議論のハイライト
- API設計の一貫性: @rsc が
CgroupFD/UseCgroupFDの命名規則に倣い、UseLandlock bool、LandlockFD int、LandlockFlags uintptrという名称を提案。当初のLandlockRuleset intから整理された。 NoNewPrivsの独立化: 当初の実装では Landlock 適用時にPR_SET_NO_NEW_PRIVSを自動的に呼ぶ設計だったが、@rsc の指摘により「常に必要とは限らない」として独立したフィールドNoNewPrivs boolに分離。これにより既にフラグが設定済みの親プロセスの子では省略できる。- 定数定義の問題:
syscallパッケージのzsysnum_linux_*.goは現在再生成が意図されておらず、新しいシステムコール番号(SYS_LANDLOCK_RESTRICT_SELF)や定数(PR_SET_NO_NEW_PRIVS)を多くのアーキテクチャ向けに手動で追加する必要があった。internal/syscall/unixはインポートループのため利用不可。@ianlancetaylor より「非公開定数として手動定義する」方針が確認された。 - 二重forkによる代替案: @randall77 が「親プロセスが自分自身をforkし、forkされたコピーがLandlockを設定してから子プロセスをexecするという二重forkでも実現できるのでは」と指摘。これに対し @gnoack は「Goランタイムがマルチスレッドのためforkのみは安全ではなく、
syscall.ForkExecが fork+exec を組み合わせた安全な形で提供されている」と回答。 - 汎用的なsyscall指定機構の検討: @aclements と @ianlancetaylor が「
SysProcAttrのフィールドが増え続ける問題を解決するため、fork/exec 間に実行するシステムコールをリストとして指定できる汎用機構(例:[]struct{syscallNum int, args []any})を設けることも検討に値する」と議論。複雑な既存フィールド(UidMappings、AmbientCaps等)との整合性や実行順序の制御が課題として挙げられており、引き続き検討中。