
eBPF LSMベースの強制アクセス制御とジェイラー
Linux向けのeBPFベースの強制アクセス制御。
このプロジェクトはクローズドソースのBpfJailerの完全な書き直しであり、 完全に実験的なものです。内部のBpfJailerが書かれた時点では利用できなかった bpf arenaのような新しい機能を活用しています。問題は想定されており、バグバウンティの 対象やセキュリティ上の発見とは見なされません。適切に評価された後、内部の クローズドソース版を置き換える予定です。
BpfJailerはeBPF LSMプログラムを使用してプロセスをjail(ポッドと呼ぶ)に配置し、
各ポッドはTOMLポリシーのロールにバインドされます。ポッドはforkとexecを
通じて継承されます。オプションのポリシー機能:
killとptrace — シグナル送信やアタッチが可能な対象のロール/ポッド。bpf — どのロールのeBPFマップとプログラムを開けるか、あるいは
bpf(2)を呼び出せるかどうか。keyring — どのロールのfs-verityキーリングに証明書を追加できるか、
あるいはキーリングに書き込めるかどうか。拒否とライフサイクルイベントはピン留めされたリングバッファに書き込まれます。bpfjlog
は人間が読めるBPF診断と構造化イベントを出力し、ライブポリシー置換を通じて
リングバッファを追跡します。
バイナリはuser.bpfj.policy.exec xattrを通じてロールを要求でき、exec時に
そのロールに登録されます。実行中のプロセスも直接登録でき、非特権プロセスは
bpfjsrv/bpfjclientを通じて自身を登録できます。
| ディレクトリ | バイナリ | 目的 |
|---|---|---|
bpfj/ | コアライブラリとBPFプログラム: jailer、enforcer、ポリシーパーサー、libbpf C++ヘルパー。 | |
ctl/ | bpfjctl | jailerのアタッチ、リロード、検査、デタッチ、およびプロセスの登録のための汎用ツール。 |
cmd/ | bpfjcmd | 引数、およびオプションでポリシーをコンパイル済みにしたbpfjctl。argvを無視するため、静的にリンクして単一ユニットとしてfs-verity署名できます。 |
srv/ | bpfjsrv | ソケット起動型サーバーで、非特権呼び出し元をそれを許可するロールに登録します。 |
client/ | bpfjclient | bpfjsrv用の最小クライアントで、libbpfやBPFツールチェーンへの依存がありません。 |
log/ | bpfjlog | ピン留めされた診断および構造化イベントリングバッファのコンシューマー。 |
tests/ | bpfjtest | テストスイート。 |
CONFIG_BPF_LSM=yおよびlsm=ブートパラメータに
bpf)。BpfJailerは6.16以降でのみテストされており、古いカーネルは
サポートされていません。bpftool、およびC++20コンパイラ。openssl、fsverity、setfattr、およびMakefileに
記載されている静的アーカイブ(STATIC=1)。pkg-configがlibbpfを見つけられない場合はLIBBPF_CFLAGS / LIBBPF_LIBSを設定してください。
すべてのビルドでLIBARENAをlibarenaチェックアウトに指定してください:
以下のすべてのmakeにはLIBARENA(要件を参照)も必要で、コマンドラインで
設定するか環境にエクスポートしてください。
make # build/bpfjctl
make STATIC=1 # 共有オブジェクト依存のないbpfjctl
make client # build/bpfjclient、BPFツールチェーン不要
make log # build/bpfjlog
make signing-key # 開発用署名鍵と証明書を生成
make signed SIGNING_KEY=... SIGNING_CERT=... # 静的、fs-verity署名済みbpfjctl
make srv SIGNING_KEY=... SIGNING_CERT=... # 静的、署名済みbpfjsrv
make cmd SIGNING_KEY=... SIGNING_CERT=... \
CMD_ARGS="replace-compiled" CMD_POLICY=policy.toml CMD_ROLE=bpfjailer
make clean
すべての出力はbuild/以下に置かれます。別の場所でビルドするにはBUILD=を設定します。
例えばmake BUILD=build-asan SANITIZE=address,undefined。
make test
テストはrootとして実行する必要があります。各テストがマウント名前空間を作成し、
bpffsをマウントするためです。make testは実行ユーザーとしてビルドし、
sudoの下でテストバイナリのみを実行します。
テストはデフォルトで直列に実行されます。並行するBPF LSMデタッチが影響を受ける
カーネルをパニックさせる可能性があるためです。集中的なケースには
make test TEST_ARGS=Suite.Testを使用し、使い捨てVM内でのみ-j Nまたは
BPFJTEST_JOBS=Nをオプトインしてください。
sudo bpfjctl check policy.toml # ポリシーを解析し、その内容を報告
sudo bpfjctl attach policy.toml # jailerをロードしてピン留め
sudo bpfjctl replace policy.toml # jailされたタスクを解放せずにリロード
sudo bpfjctl wrap ROLE USER_ID -- CMD # 新しいポッドでCMDを実行
sudo bpfjctl enroll ROLE USER_ID PID [NAME=VALUE...] # 変数付きで登録
sudo bpfjctl show PID # プロセスが属するポッド
sudo bpfjctl list # すべてのポッドとそのプロセス
sudo bpfjctl detach # ピンを解除してアンロード
プログラムはデフォルトで/sys/fs/bpf/bpfj-pins以下にピン留めされます。これを変更するには
--bpffs-pathと--pin-dirを使用します。detachが実行されるまでロードされたままです。
--drop-capなしで非rootの--uidを指定したbpfjctl wrapは、コマンドが
自身をjailから削除できる状態のままにします。bpfjctl wrap --helpを参照してください。
jailerがアタッチされている間にsudo build/bpfjlogを実行して観察します。BPF
診断はstderrに、構造化イベントはstdoutに書き込まれます。ロガーはreplaceが
新しいピン留めマップのセットをスワップインすると自動的に再接続します。
replaceはアクティブなものの隣に完全な2番目のjailerをロードし、ポッド
メンバーシップ、変数、追跡されたリソース所有権を移行してから、ピンツリーを
アトミックにスワップします。ハンドオフ中は両方のツリーがアタッチされたままで、
forkと登録は移行と調整され、所有権の変更はジャーナルされリプレイされます。
永続化されたレイアウトバージョンに互換性がない場合、または状態を安全に
コピーできない場合、置換はフェイルクローズします。
base-role = "floor" # オプション: ホスト上のすべてのプロセスを登録
vars = ["vm_uuid"] # 既知の変数名
[certs]
corp-ca = "MIIDXTCCAkWgAwIBAgIJAK..." # PEMまたはbase64 DER証明書
[roles.floor]
any = true # 追跡専用のベースロールを開く
[roles.webserver]
enforce-binary-certs = ["corp-ca"] # execはこれらのいずれかで署名されている必要がある
kill-roles = ["floor"] # 自身のポッド、およびこれらのロールにシグナル送信可能
ptrace-pod = true # 自身のポッドのみ
proc-roles = ["floor"] # これらのロールのprocファイルを開くことが可能
bpf-pod = true # 自身のポッドのBPFオブジェクトのみ
lkm-any = false # モジュールとkexecのローディングを拒否
mq-sysv-pod = true # 自身のポッドのSysVキュー
mq-posix-pod = true # 自身のポッドのPOSIXキュー
shm-sysv-pod = true # 自身のポッドのSysV SHM
shm-posix-pod = true # 自身のポッドのPOSIX SHM
keyring-own = true # 自身のロールのキーリングのみ
[[roles.webserver.mq-posix-pattern]]
name = "/service-${vm_uuid}-*"
allow = true
[[roles.webserver.shm-posix-pattern]]
name = "/service-${vm_uuid}-*"
allow = true
[[roles.webserver.exec-paths]]
path = "/usr/bin/webserver"
allow = true
permissions = ["exec"]
[[roles.webserver.exec-paths]]
path = "/usr/lib"
allow = true
permissions = ["shared-object"]
[[roles.webserver.paths]] # キャッシュされたパスポリシー
path = "/"
allow = false
[[roles.webserver.paths]]
path = "/usr"
allow = true
access = "read-only"
[[roles.webserver.paths]]
path = "/etc"
allow = true
access = "read-only"
[[roles.webserver.paths]]
path = "/srv/web"
allow = true
access = "read-write"
[[roles.webserver.unix-bind]] # パス名bindルール
path = "/run/webserver"
allow = true
[[roles.webserver.unix-connect]]
name = "@control-${vm_uuid}"
allow = true
[[roles.webserver.unix-dgram]]
path = "/dev/log"
allow = true
[[roles.webserver.mount]] # 宛先と許可されるファイルシステムタイプ
path = "/srv/data"
allow = true
filesystems = ["ext4", "xfs"]
[[roles.webserver.mount]]
path = "/run/webserver"
allow = true
filesystems = ["any"] # この宛先でのすべてのファイルシステムタイプ
[[roles.webserver.umount]]
path = "/"
allow = false
[[roles.webserver.umount]]
path = "/srv/data"
allow = true
[roles.sandbox]
unpriv-enroll = true # 指定されていないすべての操作は拒否されたまま
override-stacked = true # 下にスタックされたロールを無視して単独で応答
ほとんどの操作ゲートは、ロールに対応するオプションがない場合に拒否されます。
*-podオプションは同じポッドからのリソースを許可し、*-rolesは指定された
所有者ロールを追加し、*-anyはその操作を完全に開きます。keyring-ownは
ロールスコープの対応物です。fs-verityキーリングはポッドではなくロールに
属するためです。enroll-rolesはbpfjsrvが追加できる唯一のロールを指定します。
これがない場合、bpfjsrvを通じた登録は拒否されます。Unixパス名、マウント、
アンマウント操作は、そのオプションが存在しないか、一致するパスがない場合に
拒否されます。抽象Unixソケット名はオプトインフィルタのままであるため、
設定されていない、または一致しない抽象名は許可されます。
完全に開かれたprocオプションはproc-anyで、他の所有権ファミリーと同じ
*-anyの順序に従います。
any = trueは、より具体的なオプションを持たないすべての操作を開きます。これは
帰属のみに使用されるポッドに便利です。bpf-pod、kill-roles、paths、
enforce-binary-certsのようなスコープ付きオプションは、その操作についてanyを
上書きします。lkm-any、fs-any、verity-any、mount-any、umount-anyは
操作固有の完全に開かれた形式です。
複数のロールを保持するプロセスは、すべてのロールが同意した場合にのみ操作が
許可されます。ロールは新しい順に参照され、override-stackedロールはその下の
ロールに対して応答します。killとptraceのターゲット側はoverrideを無視します。
ターゲットが保持するすべてのロールがリストされている必要があります。完全な
セマンティクスはbpfj/policy/Policy.hに文書化されています。
varsは許可リストです。ポリシーがリストしていない変数を設定する登録は拒否され、
varsがまったくない場合はどのポッドも変数を保持しません。replaceは各ポッドの
変数を名前で引き継ぎ、新しいポリシーがポッドが保持している変数をリストしなく
なった場合は失敗します。
さまざまなポリシーオプションがパスとglobを取ります。これらはすべて共通の マッチャーに従います。変数は${NAME}で示され、ワイルドカード?と*がサポート されています。これにより、特定のポッド内の変数にスコープされたオプションの マッチングが可能になり、例えば2つのコンテナが互いに異なるファイルシステム 権限と分離ルールを持つことができます。すべてのファイルシステム操作は現在、 マウント操作がマッチャーに影響を与えないようにsystemdのマウント名前空間で 実行されます。その名前空間で解決できないファイルは許可されます。一致する 名前空間の選択は近日対応予定です。
完全なオプションマトリックス、マッチングセマンティクス、置換動作については POLICY.mdを参照してください。