libcのopenはopenat(2)を呼ぶ
eBPFを試してみた。 Pythonのサンプルコードを拾ってきてシステムコールをフックしてみた。 ところが open(2) をフックできなかった。一方で openat(2) はフックされた。 環境はこれ。
|
|
システムコールによってkprobeで登録後に実際にイベントが来るものと来ないものがあった。 まとめると下のようになる。本当に基本的なシステムコールでしか試してない。
システムコール | kprobe経由でイベントを受け取れた |
---|---|
clone | TRUE |
fork | FALSE |
open | FALSE |
openat | TRUE |
close | TRUE |
調べたところlibcの中で open(3) が openat(2) を呼び出していた。
具体的にはlibc/sysdeps/unix/sysv/linuxの open.c, openat.c, open64.c, openat64.c
に書かれている。
fork も同様に clone(2) を呼び出していた。
再現方法
システムコールの呼び出しをトレースするスクリプト、システムコールを呼び出すターゲットプログラムの2つを準備する。
トレースするスクリプトはサンプルコードを真似て書いた。
下のスクリプトを準備してsudo python3 ./example.py
する。
(実際には close(2) はgrepで除外した。)
|
|
open(2) が見えなかったのでCで明示的に open(2) を呼び出すプログラムを作って確認した。
|
|
gcc ./target.c -o target
でコンパイルして叩く。
したみたいな事になる。 clone(2), openat(2) が呼ばれている。
kprobeのフックポイントより前に open(2) が openat(2) に変わっている。
|
|
調べたこと: 変換場所
open(2) が openat(2) になる場所を探した。候補はlibcとカーネル内部。
- kprobe , ptrace で具体的に何を取得しているか?
- Cで open() を読んだときに具体的に何が呼ばれるか?
あたりを調べた。
まず strace 経由でシステムコールを確認した。 これによると ptrace でも openat(2) が見えている。
|
|
結論
libc の中で open(3) が openat(2) を呼び出していた。具体的にはlibc/sysdeps/unix/sysv/linuxの open.c, openat.c, open64.c, openat64.c に書かれている。 fork(3) も同様に clone(2) を呼び出していた。
open64.c の場合は下のようになっている。ここで SYSCALL_CANCEL はsysdeps/unix/sysdep.hで定義されていて実際にシステムコールを発行するマクロに展開されていく。
|
|
補足と感想
カーネルの側 でも同様に open(2) の実際の処理である do_sys_open() が do_sys_openat() を呼び出している。
open(2) のハンドラ定義は直下の
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
が open(2) でそのなかで do_sys_open() を呼んでいる。
今回のeBPFはKprobeを使いftraceを利用してシステムコールの実行イベントをフックして動いている。 sys_open シンボルの先頭がフックされる場所で通過してないのは当然だ。ここからもlibcで置き換えられていると疑うのが自然だった。ちなみにftraceではftrace_modify_code_directがカーネル内の命令を置き換えている。
実際にlibcを疑い始めたのは ptrace(2) で PTRACE_GETREGS を使った際に openat(2) しか取れなかったため。 実際にカーネル側を確認したところユーザープロセスのレジスタを普通にコピーしてそうなオフセットテーブルがあった。となるとユーザープロセスの %rax レジスタは呼び出し時から変わってないはず。そうなるとカーネル内部で do_sys_openat の呼び出しがされても関係がない。
なかなかに判断が遅れた。Linuxもlibcも読み慣れてないのでもっと読んだりフックしたりして遊んでおきたい。 デバッグハックも読み返そうと思った。
最近はScrapboxに書いて放置が多い。 これももともとここに書いたものからコピペして編集した。 PlantUMLとかを簡単に利用できて気楽に書けるUI、アクセスの確認と検索ができてテキストとして管理できるのが嬉しい。 やはりZennだろうか。
参考資料
Kprobeの使い方や他のフック方法との比較などは下が参考になった。