同じ「拒否」でも確認する対象が違う

プロセスの要求から、カーネル内部の実行までを分けて見る

誰が要求したか身元と権限

UID、GID、capabilitycapability従来rootへまとめて与えられていた権限を、操作の種類ごとに分けた単位。などをcredentialcredentialカーネルがプロセスの身元と権限を確認するために保持する情報。として保持します。

何を要求したかカーネルへの依頼

システムコールシステムコールユーザー空間のプロセスが、Linuxカーネルへ処理を頼むための入口。の種類と引数をseccompseccompプロセスが呼び出せるシステムコールと引数をfilterで制限する仕組み。で絞れます。

何へアクセスするか操作の対象

ファイル、socket、プロセスなどを対象に権限を確認します。

脆弱性があった後カーネル自身の防御

メモリ破壊や実行経路の乗っ取りを検出し、悪用を難しくします。

09.1
最初に、要求する側と対象を照合する

UID・GIDとファイルの権限から始まる

プロセスがファイルを開いたり、別のプロセスへシグナルシグナルカーネルや別のプロセスが、プロセスへイベントの発生を非同期に知らせる仕組み。を送ったりするとき、カーネルは要求したプロセスの身元と権限を調べます。LinuxはUID、GID、補助グループ、capability、LSMLSMカーネル内の操作へ、アクセス制御を呼び出すhookを用意する共通の枠組み。が使うセキュリティ属性などをプロセスに保持します。これらをまとめたカーネル内の情報をcredentialcredentialカーネルがプロセスの身元と権限を確認するために保持する情報。と呼びます。

ファイル側には所有者のUIDとGID、所有者・group・その他に対するread・write・executeのmode、必要に応じてACLがあります。プロセスとファイルの情報を照合する基本のアクセス制御をDACDAC所有者が設定できる権限をもとに、対象へのアクセスを確認する仕組み。と呼びます。

要求するプロセス

credential

effective UID 1000effective GID 100補助グループcapabilityの集合LSMが使うセキュリティ属性
readを許可できるか照合
対象のファイル

/srv/report.txt

owner UID 1000group GID 200mode rw-r-----POSIX ACLLSMが使うセキュリティ属性
Linux 2.2から

rootの強い権限を操作の種類ごとに分ける

従来のUnixでは、effective UID 0のプロセスを特権プロセスとして扱いました。Linuxのcapabilitycapability従来rootへまとめて与えられていた権限を、操作の種類ごとに分けた単位。は、その権限を独立した単位へ分けます。

  • CAP_NET_BIND_SERVICE1024未満のportへbindする
  • CAP_SYS_TIMEシステム時刻を変更する
  • CAP_SYS_PTRACE別のプロセスをtraceする
  • CAP_BPF特権が必要なBPF操作を行う
09.2
一つのファイル操作を入口から追う

seccomp、DAC、LSMは別の場所を確認する

アプリは、ファイル、通信、プロセスなどをカーネルに操作してもらうため、システムコールを使います。プロセスが使えるシステムコールと引数をfilterで絞る仕組みをseccompseccompプロセスが呼び出せるシステムコールと引数をfilterで制限する仕組み。と呼びます。

通常のファイル権限に加えて、システム全体やアプリ固有の方針も確認できます。カーネル内の操作箇所へ、アクセス制御を呼び出す共通のhookを用意する枠組みがLSMです。

ユーザー空間openat()アプリがファイルを指定
→
システムコール入口seccomp番号と引数を検査
→
通常の権限確認DACUID・GID・mode・ACL
→
追加のアクセス制御LSM hooksSELinux・AppArmor・Landlock
→
許可された処理ファイルを開くファイルディスクリプタを返す
アプリから要求する

openat()でファイルを指定する

プロセスは、ディレクトリを示すファイルディスクリプタ、パス、読み書きの指定をカーネルへ渡します。

LSMのhookでは、有効になっているsecurity moduleがそれぞれの方針を確認します。現在のLinuxでは複数のLSMを重ねて使う構成もあります。

labelで判断

SELinux

プロセスとファイルなどへsecurity contextを付け、typeなどと操作の組み合わせをpolicyで許可します。

taskのprofileで判断

AppArmor

実行するプログラムへprofileを対応づけ、path、network、capabilityなどの操作を記述します。

プロセスが自分を制限

Landlock

一般ユーザーのプロセスもrulesetを作り、自分と子孫がアクセスできる対象を狭められます。

09.3
侵害されたプロセスが届く範囲を狭める

seccompとLandlockは異なる対象を制限する

システムコールの入口

seccomp

seccompseccompプロセスが呼び出せるシステムコールと引数をfilterで制限する仕組み。は、システムコール番号と値として渡された引数をfilterへ通します。許可、errnoの返却、プロセス終了、trace、ユーザー空間への通知などの結果を返せます。

socket()mount()ioctl()bpf()
アクセスする対象

Landlock

LandlockLandlock一般ユーザーのプロセスも、自分と子孫のアクセス権を狭められるLSM。は、ファイル階層、network port、signal、UNIX domain socketなど、カーネルが管理する対象へのアクセスをrulesetで狭めます。

/usr/**TCP :443signal scopeUNIX socket

no_new_privsno_new_privsexecve後も、現在より強い権限を得ないとカーネルへ約束する属性。を設定すると、その後のexecve()でset-user-IDやfile capabilityから新しい権限を得ないことをカーネルへ約束します。一般ユーザーがseccomp filterやLandlockを適用するときの前提として使われます。

Linux 5.13のファイル制限から、Linux 7.2のABI 10へ

対象の追加と、実運用に必要な機能の整備が続く

初期のLandlockはファイル階層を制限しました。その後、TCP、デバイスのioctl、signal、UNIX domain socket、UDPへ対象が広がり、拒否理由のaudit log、同じプロセスに属する全スレッドへの一括適用、ログ抑制も加わりました。アプリは実行中のカーネルからABI versionABIコンパイル済みのプログラムとOSやlibraryが、binary levelでやり取りするための約束。を取得し、そのカーネルが扱える権限だけでrulesetを作ります。

Linux 5.13ABI 1

ファイル

実行、読み書き、作成、削除をディレクトリツリーごとに制限

Linux 5.19ABI 2

移動とlink

ディレクトリをまたぐrenameとlinkを個別に許可

Linux 6.2ABI 3

truncate

ファイルの長さを変更する操作を制限

Linux 6.7ABI 4

TCP

bindとconnectをport番号で制限

Linux 6.10ABI 5

デバイスのioctl

character deviceとblock deviceのioctlを制限

Linux 6.12ABI 6

signalとIPC

signalとabstract UNIX socketの範囲を分ける

Linux 6.15ABI 7

audit

拒否した操作と理由をaudit logへ記録

Linux 7.0ABI 8

全スレッド

同じプロセスに属する全スレッドへ同時にrulesetを適用

Linux 7.1ABI 9

UNIX socket

pathname UNIX socketへの接続を制限

Linux 7.2ABI 10

UDPとログ抑制

UDPのbindと送信先、audit logを抑える対象を指定

横へスクロールしてABI 10まで確認できます。

09.4
カーネル内のバグが悪用される段階を増やす

攻撃の入口、情報漏えい、メモリ破壊、制御の乗っ取りを分けて防ぐ

カーネルへ到達できる入力を減らしても、使う機能のコードには脆弱性があり得ます。Linuxのself-protectionは、バグを悪用して権限を奪うまでに必要な条件を増やし、途中で検出または阻止する仕組みを重ねます。

01 / 到達

脆弱な処理へ入力を届ける

入口を減らす

seccomp、権限確認、未使用機能の無効化

→
02 / 情報

配置や内部データを知る

情報を隠し、配置を変える

KASLR、kernel pointerの公開制限、初期化

→
03 / 破壊

境界外へ書き込み、状態を壊す

検出し、書ける範囲を狭める

stack canary、FORTIFY、read-only data、strict RWX

→
04 / 実行

命令の流れを乗っ取る

制御の流れと行き先を検証する

KCFI、kernel IBT、SMEP・SMAP、PAN

確率に依存する緩和

KASLR

起動ごとにカーネルの配置を変えます。配置が漏れれば効果が下がるため、情報漏えい対策と組み合わせます。

CPUの脆弱性への対応

KPTI

Meltdownへの対策として、ユーザー空間を実行するときに見えるカーネル側のページテーブルを最小限へ分けます。

x86のCET

カーネルはIBT、ユーザー空間はshadow stack

現在の64-bit x86 Linuxでは、カーネルの間接分岐をIBTで検査します。shadow stackはユーザー空間のreturn addressを保護する機能として提供されています。

stack canary

関数のstack上で、local variableとreturn addressの間に検査用の値を置きます。関数から戻る前に値が変わっていないかを確認します。

FORTIFY_SOURCE

compilerがbufferの大きさを判断できる箇所で、文字列やmemoryのcopyが範囲を越えないかを検査します。

strict RWX

kernel codeを実行可能かつ書き込み不可にし、書き換えられるdataを実行不可にします。read-only dataへの書き込みも防ぎます。

KCFI

function pointerを使う間接呼び出しで、呼び出し先のfunction typeが期待した型と合うかを検査します。

kernel IBT

x86 CPUが、間接的なcallやjumpの行き先として許された位置かを確認します。

SMEP・SMAP / PAN

kernelがuser spaceのmemoryを意図せず実行したり、読み書きしたりする経路をCPUの機能で制限します。

利用できる防御はCPUアーキテクチャ、コンパイラ、カーネル設定によって変わります。KASLRだけ、stack canaryだけで完結するものではなく、異なる段階を扱う機能を組み合わせます。

09.5
カーネルへ新しいコードを追加する経路を制限する

kernel moduleの署名を読み込み時に確認する

配布する側module.ko秘密鍵で署名を付ける
→
カーネル公開鍵で検証署名と内容を確認する
→
設定に応じた結果読み込み / 拒否強制時は未署名moduleを拒否

kernel moduleはカーネル空間で動くコードです。module signingは、moduleへ暗号学的な署名を付け、読み込み時にカーネルが検証します。CONFIG_MODULE_SIG_FORCEまたはmodule.sig_enforce=1を使う構成では、信頼する公開鍵で検証できないmoduleを拒否します。

第10章で、サービス単位の設定へ対応づける