メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#68595likely_accept

syscall: support process sandboxing using Landlock on Linux

ステータス変更: active likely_accept

要約

AIによる要約であり、誤りを含む場合があります。

概要

syscall.SysProcAttr 構造体に Landlock LSM(Linux Security Module)のサポートを追加し、Goプログラムが起動するサブプロセスに対してのみファイルシステムサンドボックスを適用できるようにする提案です。

ステータス変更

activelikely_accept
2026年6月10日に開催されたProposal Review Meetingにおいて、@aclements、@cherrymui、@griesemer、@ianlancetaylor らが参加するミーティングで「likely accept(ほぼ受理)」と判定されました。@rscによるAPIの設計レビューと @gnoack による継続的な改訂・実装(CL #745940、PR #77630)を経て、APIの構造が固まり、コアチームの合意が形成されました。

技術的背景

現状の問題点

Landlock LSM はLinuxカーネルが提供する「非特権サンドボックス機能」であり、プロセスがアクセスできるファイルシステムのパスを制限できます。多くのLinuxディストリビューションでデフォルト有効化されています。
問題は、Goプログラムが起動するサブプロセスだけにLandlockポリシーを適用する方法が存在しないことです。現状では、呼び出し元Goプロセス全体に landlock_restrict_self(2) を適用するか、複雑な「自分自身を再起動して制限を適用してから目的のプログラムをexecする」というワークアラウンドを取るしかありません。
Goのマルチスレッドランタイムの制約上、fork() のみを安全に実行することができず(os/exec の内部実装にある forkAndExecInChild1() は fork と exec の間でのみ安全な処理が可能)、これが問題をより困難にしています。
具体的なユースケースとして、提案者は「Goバイナリが信頼できないgitリポジトリを処理するため、git コマンドを何十万回も呼び出す。この git サブプロセスは特定の一時ディレクトリしか触れるべきではないが、Goプロセス自体はネットワークや多くのファイルにアクセスする必要がある」というシナリオを挙げています。

提案された解決策

syscall.SysProcAttr 構造体(Linuxのみ)に以下の4つのフィールドを追加します:

type SysProcAttr struct {
    // ... 既存フィールド ...
    // UseLandlock: exec前にlandlock_restrict_self(LandlockFD, LandlockFlags)を呼び出す
    // NoNewPrivsと組み合わせることが推奨される
    UseLandlock   bool
    LandlockFD    int     // Lanldockルールセットのファイルディスクリプタ
    LandlockFlags uintptr // landlock_restrict_self(2)に渡すフラグ
    NoNewPrivs bool // exec前にprctl(PR_SET_NO_NEW_PRIVS)を呼び出す
}

実装的には、forkAndExecInChild1() 関数内の fork と exec の間で RawSyscall() を使って landlock_restrict_self(2) と必要に応じて prctl(PR_SET_NO_NEW_PRIVS, ...) を呼び出します。この変更の鍵は「サブプロセスのみ」に制限が適用される点で、親Goプロセスは影響を受けません。
NoNewPrivs フィールドは既存の AmbientCaps フィールドと同じカテゴリに分類されます。

これによって何ができるようになるか

  • 最小権限原則の適用: 親プロセスの権限に影響を与えずに、サブプロセスのファイルシステムアクセスを特定のディレクトリのみに制限できます
  • ライブラリコードでの利用: 自身を再起動できないライブラリコードからも、安全にサンドボックス付きサブプロセスを起動できます
  • go-landlockライブラリとの連携: github.com/landlock-lsm/go-landlock のような上位ライブラリからルールセットFDを受け取り、低レベルAPIとして機能します

コード例

// Before: 従来のワークアラウンド(自分自身を再起動してLanldockを設定し、
// 目的のプログラムをexecするという複雑な手順が必要だった)
// ライブラリコードでは実質不可能
// After: 新APIを使った書き方
// go-landlockライブラリでルールセットFDを構築
rulesetFD, flags, err := landlock.V8.BestEffort().AsRuleset().Restrict(
    landlock.RODirs("/"),     // /以下は読み取りのみ
    landlock.RWDirs("/tmp"),  // /tmpは読み書き可
)
if err != nil {
    // ...
}
defer syscall.Close(rulesetFD)
// サブプロセスにのみLanldockポリシーを適用
cmd := exec.Command("git", "clone", untrustedURL, tmpDir)
cmd.SysProcAttr = &syscall.SysProcAttr{
    NoNewPrivs:    true,
    UseLandlock:   true,
    LandlockFD:    rulesetFD,
    LandlockFlags: flags,
}
if err := cmd.Run(); err != nil {
    // ...
}
// 親のGoプロセスのファイルアクセスは無制限のまま

議論のハイライト

  • APIの命名: 当初は LandlockRuleset int というフィールド名が提案されていたが、@rscが既存の CgroupFD/UseCgroupFD フィールドとの一貫性を考慮して UseLandlock/LandlockFD への改名を提案し、採用された
  • NoNewPrivs の独立化: 初期実装では UseLandlock=true のとき自動的に prctl(PR_SET_NO_NEW_PRIVS) も呼び出していたが、「常に必要とは限らない」という @rscの指摘により、独立したフィールドとして切り出された
  • 汎用的なsyscallリスト機能の検討: @aclements と @ianlancetaylor は「個別フィールドを追加するたびに SysProcAttr が肥大化する」という観点から、fork〜exec間で任意のsyscallを実行できる汎用機構の検討を議論した。ただし UidMappings のような複雑なケースへの対応やGCとの型安全性の問題があり、現時点ではLanldock向けの具体的なフィールド追加の方が現実的と判断されている
  • syscall定数の扱い: syscall パッケージは実質的にフリーズされており、自動生成ファイルの再生成は意図されていない。そのため _PR_SET_NO_NEW_PRIVS_SYS_landlock_restrict_self などの定数は非公開の形で手動追加する方針が @ianlancetaylor により示された
  • Goのfork制約: GoのマルチスレッドランタイムではGoランタイムのスレッドが複数存在するため、fork() 単体での呼び出しは安全でない。SysProcAttr による fork〜exec間での操作こそがこのユースケースを実現する唯一の適切な方法であることが確認された

関連リンク