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

Go Proposal Weekly Digest

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

#68595active

syscall: support process sandboxing using Landlock on Linux

新規提案

要約

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

概要

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_PRIVSSYS_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 boolLandlockFD intLandlockFlags 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})を設けることも検討に値する」と議論。複雑な既存フィールド(UidMappingsAmbientCaps 等)との整合性や実行順序の制御が課題として挙げられており、引き続き検討中。

関連リンク