strace
Linuxのシステムコールトレーサー。プロセスが発行するシステムコールとその引数・戻り値、受信したシグナルを人間可読な形で表示するコマンドラインツール。1991年にPaul KranenburgがSunOS向けに書いたものが起源で、翌年Branko LankesterがLinux移植とカーネル側のptraceサポートを書いた。 #linux #security
実装原理: PTRACE_SYSCALLでシステムコールの境界ごとに止める
内部的にはptraceのPTRACE_SYSCALL操作を使う。ptrace(2)によると、トレーシーをこのオペレーションで再開すると、次のシステムコールの入口または出口で自動的に停止する。
- straceが
fork()→子でPTRACE_TRACEME→execve()(新規起動の場合)、または既存プロセスにPTRACE_ATTACH(-pオプションの場合) PTRACE_SYSCALLでトレーシーを再開- トレーシーが次にシステムコールに入る瞬間(syscall-enter-stop)で自動停止
- straceが
PTRACE_GETREGSETなどでレジスタを読み、システムコール番号と引数を取得・表示 - 再び
PTRACE_SYSCALLで再開すると、今度はシステムコールが完了した瞬間(syscall-exit-stop)で止まる - straceが戻り値を読んで
) = 結果の部分を表示し、3に戻る
レジスタの読み方はアーキテクチャ依存。man 2 syscallのcalling conventionテーブルによれば、x86-64ではシステムコール番号がrax(enter時)、引数がrdi/rsi/rdx/r10/r8/r9、戻り値がrax(exit時)に入る。straceはこれをアーキテクチャごとのテーブルとシステムコール名/引数の型定義に突き合わせてopen("/etc/fuse.conf", O_RDONLY) = 3のような表示に変換している。
strace本体のメインループは公式ドキュメント(doc/INTERNALS.md)によれば非常に単純。
while (dispatch_event(next_event()))
;
next_event()がwait4()で停止イベントを待ち、dispatch_event()がsyscall-stopならtrace_syscall()でデコード・表示し、ptrace_restart()で再開する。
落とし穴: enter-stopとexit-stopは見分けがつかない
doc/README-linux-ptraceによると、この2種類の停止はトレーサー側からは本質的に区別できない(同じSIGTRAPに見える)。straceはPTRACE_O_TRACESYSGOODオプションをPTRACE_SETOPTIONSで立てることで対処している。これを立てるとsyscall-stopの時だけWSTOPSIG(status) == (SIGTRAP | 0x80)という見分けやすい値になり、信頼性が高くオーバーヘッドもない。PTRACE_GETSIGINFOのsi_codeを見る方法やレジスタの値で判定する方法もあるが、いずれも「fragile(壊れやすい)」と明記されている。
--seccomp-bpfオプション: オーバーヘッド対策
通常モードは全システムコールで停止するためコンテキストスイッチが2倍(enter+exit)に増え、トレース対象のプロセスは体感できるほど遅くなる。--seccomp-bpf(-fと併用時のみ有効)を使うと、seccompのSECCOMP_RET_TRACEアクションを使うBPFフィルタをトレーシーに注入し、カーネル側で「見る必要のあるシステムコールだけ」を選別してからptrace-stopを発生させる。フィルタ対象外のシステムコールはstopなしで直接実行されるため、大幅にオーバーヘッドを削減できる。
setuidとの相互作用
ptraceされているプロセスはsetuidの昇格が無効化されるに書いた通り、straceでトレースしている間は対象プロセスのsetuid/setgidによる昇格が無効化される。fuse-hello-world実験でstrace -fを使ってfusermount3(setuid-root)をトレースした際、この制約によりfusermount3自身のマウント処理までEPERMで失敗する挙動を実際に観測した。
Linuxの権限分離・権限昇格の仕組みの中での位置づけ
ptraceの代表的な利用者。ptraceされているプロセスはsetuidの昇格が無効化されるで見た「setuidとの非交差設計」がユーザーに見える形で現れる場面でもある。
出典
man 2 ptraceman straceman 2 syscall- strace/doc/README-linux-ptrace
- strace/doc/INTERNALS.md