Slide 1

Slide 1 text

eBPFとwaruiBPF Dec. 6th, 2025 Kernel/VM探検隊@北陸 Part 8 Satoru Takeuchi X: satoru_takeuchi

Slide 2

Slide 2 text

eBPFとwaruiBPF Dec. 6th, 2025 Kernel/VM探検隊@北陸 Part 8 Satoru Takeuchi X: satoru_takeuchi いいね! eBPFくん

Slide 3

Slide 3 text

eBPF ● Linuxカーネルの機能 ● カーネル本体に手を入れず、カーネル空間で安全にプログラムを動かせる ● うまくプログラムを書けば… ○ ビルド済みバイナリを複数カーネルバージョンで動かせる ○ 同、別アーキテクチャの CPUで動かせる ● 適用範囲: ○ トレース(例: bpftrace) ○ パケット処理(例: Cilium) ○ 不正動作検出&禁止(例: Falco)

Slide 4

Slide 4 text

プログラムをカーネルにロードするまでの流れ ソースコード (eBPF C, Rust) eBPF bytecode (アーキテクチャ 非依存) eBPFサブシステム clang + LLVM BPF backend 変換 eBPFのツール、ライブラリ (libbpf, cilium agent) bpf() syscall呼び出しによりロード verifier(後述) eBPF bytecode 安全性チェック ユーザ空間 カーネル空間

Slide 5

Slide 5 text

ロード後に実行する流れ eBPFサブシステム eBPF bytecode 何らかのイベント発生 ! カーネル 様々なサブシステム (1) 検出 (2) イベントに対応した ebpfプログラム呼び出し eBPF VM (3) 実行 (JITコンパイル有り)

Slide 6

Slide 6 text

イベントの例 ● システムコール呼び出し、復帰時 ● 特定のコード実行時(関数呼び出し時、特定の命令実行時、復帰時など) ○ カーネルのコードだけでなくプロセスのコードの実行にも使える ● パケット送受信時 ● ファイルシステムI/O時 ● タイマー起動時

Slide 7

Slide 7 text

eBPFプログラムの例

Slide 8

Slide 8 text

実験環境 ● CPU architecture: x86_64 ● OS: Ubuntu 24.04.3 ● kernel: 6.8.0-88-generic

Slide 9

Slide 9 text

プログラム実行時にメッセージを表示 #include #include char LICENSE[] SEC("license") = "GPL"; struct sys_enter_execve_args { unsigned long long unused; long syscall_nr; const char *filename; const char *const *argv; const char *const *envp; }; SEC("tracepoint/syscalls/sys_enter_execve") int trace_exec(struct sys_enter_execve_args *ctx) { bpf_printk("execve is called: %s\n", ctx->filename); return 0; }

Slide 10

Slide 10 text

実行すると… $ clang -O2 -g -target bpf \ -I/usr/include \ -I/usr/include/x86_64-linux-gnu \ -c test.c -o test.o $ sudo bpftool prog load test.o /sys/fs/bpf/waruibpf autoattach $ sudo cat /sys/kernel/debug/tracing/trace_pipe

Slide 11

Slide 11 text

execve()が呼び出されるたびにログが出る $ clang -O2 -g -target bpf \ -I/usr/include \ -I/usr/include/x86_64-linux-gnu \ -c test.c -o test.o $ sudo bpftool prog load test.o /sys/fs/bpf/waruibpf autoattach $ sudo cat /sys/kernel/debug/tracing/trace_pipe … (kubelet)-2944 [000] ...21 773.184329: bpf_trace_printk: execve is called: /usr/bin/kubelet ...

Slide 12

Slide 12 text

ethernetフレーム受信時にL3のプロトコルを表示 #include "vmlinux.h" #include #include char LICENSE[] SEC("license") = "GPL"; SEC("xdp") int xdp_print_eth_proto(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; if (data + sizeof(struct ethhdr) > data_end) return XDP_PASS; struct ethhdr *eth = data; __u16 proto = bpf_ntohs(eth->h_proto); bpf_printk("XDP eth proto = 0x%x\n", proto); return XDP_PASS; }

Slide 13

Slide 13 text

eth0にアタッチすると… … $ sudo ip link set dev eth0 xdp obj test.o sec xdp $ sudo cat /sys/kernel/debug/tracing/trace_pipe

Slide 14

Slide 14 text

protocolが表示された… … $ sudo ip link set dev eth0 xdp obj test.o sec xdp $ sudo cat /sys/kernel/debug/tracing/trace_pipe … -0 [002] ..s2. 1667.143565: bpf_trace_printk: XDP eth proto = 0x800 -0 [000] ..s2. 1667.159162: bpf_trace_printk: XDP eth proto = 0x800 -0 [002] ..s2. 1667.160285: bpf_trace_printk: XDP eth proto = 0x800 ... 0x800はIPv4

Slide 15

Slide 15 text

eBPFの安全装置

Slide 16

Slide 16 text

安全装置1: カーネル内のsandbox上での実行 BPF map 別のeBPFプログラム BPF map BPF map eBPFプログラム カーネル本体 eBPFヘルパー関数 読み書き 読み書き 発生したイベントのコ ンテキスト 呼び出し 読み書き(ほぼROM) データ、コード アクセス不可 アクセス

Slide 17

Slide 17 text

安全装置2: verifier ● eBPFバイトコードをカーネルにインストールする前に動作 ● バイトコードを静的解析 ● 危険と判断したプログラムのインストールを失敗させる ○ 不正なデータにアクセスする可能性がある ○ read only領域にwriteする ○ コードが長すぎる

Slide 18

Slide 18 text

verifierによりロードが失敗する例

Slide 19

Slide 19 text

実行するコードが長いとき #include #include … SEC("tracepoint/syscalls/sys_enter_execve") int trace_exec(struct sys_enter_execve_args *ctx) { volatile int dummy = 0; for (int i = 0; i < 1000000; i++) dummy++; bpf_printk("execve: %d\n", dummy); return 0; }

Slide 20

Slide 20 text

実行すると… … $ sudo bpftool prog load test.o /sys/fs/bpf/waruibpf autoattach

Slide 21

Slide 21 text

検証失敗! … $ sudo bpftool prog load test.o /sys/fs/bpf/waruibpf autoattach … 6: (61) r3 = *(u32 *)(r10 -4) ; R3_w=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff)) R10=fp0 fp-8=mmmm???? ; for (int i = 0; i < 1000000; i++) 7: (07) r1 += -1 ; R1_w=0xd9039 8: (bf) r2 = r1 ; R1_w=0xd9039 R2_w=0xd9039 9: (67) r2 <<= 32 BPF program is too large. Processed 1000001 insn processed 1000001 insns (limit 1000000) max_states_per_insn 4 total_states 11112 peak_states 11112 mark_read 1 -- END PROG LOAD LOG -- libbpf: prog 'trace_exec': failed to load: -E2BIG libbpf: failed to load object 'test.o' Error: failed to load object file make: *** [Makefile:10: run] Error 255 … 1つのeBPFプログラムは 100万命令を超えてはならない

Slide 22

Slide 22 text

ethernetフレームだけ受信するだろ?とチェックを外すと #include "vmlinux.h" #include #include char LICENSE[] SEC("license") = "GPL"; SEC("xdp") int xdp_print_eth_proto(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; //if (data + sizeof(struct ethhdr) > data_end) // return XDP_PASS; struct ethhdr *eth = data; __u16 proto = bpf_ntohs(eth->h_proto); bpf_printk("XDP eth proto = 0x%x\n", proto); return XDP_PASS; }

Slide 23

Slide 23 text

実行すると… … $ sudo ip link set dev eth0 xdp obj test.o sec xdp

Slide 24

Slide 24 text

検証失敗! … $ sudo ip link set dev eth0 xdp obj test.o sec xdp libbpf: prog 'xdp_print_eth_proto': BPF program load failed: Permission denied libbpf: prog 'xdp_print_eth_proto': -- BEGIN PROG LOAD LOG -- 0: R1=ctx() R10=fp0 ; void *data = (void *)(long)ctx->data; 0: (61) r1 = *(u32 *)(r1 +0) ; R1_w=pkt(r=0) ; __u16 proto = bpf_ntohs(eth->h_proto); 1: (69) r3 = *(u16 *)(r1 +12) invalid access to packet, off=12 size=2, R1(id=0,off=12,r=0) R1 offset is outside of the packet processed 2 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 -- END PROG LOAD LOG -- libbpf: prog 'xdp_print_eth_proto': failed to load: -13 libbpf: failed to load object 'test.o' … パケットの長さが14バイト(イーサネット ヘッダのプロトコルを示すフィールドの末 尾)より短い可能性がある

Slide 25

Slide 25 text

eBPFのpros,cons ● pros ○ カーネル空間で安全にプログラムを実行できる ■ バグってしまっているプログラム、悪意を持ったプログラムは verifierがロードさせない ○ JITコンパイラのおかげでネイティブコードを実行できる ● cons ○ verifierを通す儀式がちょっと面倒 ■ veristatやChatGPTを使えば、ある程度楽ができる

Slide 26

Slide 26 text

eBPFのpros,cons ● pros ○ カーネル空間で安全にプログラムを実行できる ■ バグってしまっているプログラム、悪意を持ったプログラムは verifierがロードさせない ○ JITコンパイラのおかげでネイティブコードを実行できる ● cons ○ verifierを通す儀式がちょっと面倒 ■ veristatやChatGPTを使えば、ある程度楽ができる いいね! eBPFくん

Slide 27

Slide 27 text

eBPFのpros,cons ● pros ○ カーネル空間で安全にプログラムを実行できる ■ バグってしまっているプログラム、悪意を持ったプログラムは verifierがロードさせない ○ JITコンパイラのおかげでネイティブコードを実行できる ● cons ○ verifierを通す儀式がちょっと面倒 ■ veristatやChatGPTを使えば、ある程度楽ができる でも… eBPFくん

Slide 28

Slide 28 text

eBPFのpros,cons ● pros ○ カーネル空間で安全にプログラムを実行できる ■ バグってしまっているプログラム、悪意を持ったプログラムは verifierがロードさせない ○ JITコンパイラのおかげでネイティブコードを実行できる ● cons ○ verifierを通す儀式がちょっと?面倒 ■ veristatやChatGPTを使えば、ある程度楽ができる ちょっとじゃ なくない? eBPFくん

Slide 29

Slide 29 text

eBPFとwaruiBPF Dec. 6th, 2025 Kernel/VM探検隊@北陸 Part 8 Satoru Takeuchi X: satoru_takeuchi 悪いぜ! waruiBPFくん

Slide 30

Slide 30 text

もうverifierのご機嫌を伺うのは嫌だ! なんとかインチキできませんか waruiBPFくん

Slide 31

Slide 31 text

waruiBPFを作るぞ!

Slide 32

Slide 32 text

verifierのエントリポイント … static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size) { … err = security_bpf_prog_load(prog, attr, token, uattr.is_kernel); if (err) goto free_prog_sec; /* run eBPF verifier */ err = bpf_check(&prog, attr, uattr, uattr_size); if (err < 0) goto free_used_maps; prog = bpf_prog_select_runtime(prog, &err); if (err < 0) goto free_used_maps; … kernel/bpf/syscall.c:

Slide 33

Slide 33 text

verifierを呼ばなければいいのでは? diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index a1f18681721c..199529d79ecf 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2766,9 +2766,9 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size) goto free_prog_sec; /* run eBPF verifier */ - err = bpf_check(&prog, attr, uattr, uattr_size); - if (err < 0) - goto free_used_maps; + //err = bpf_check(&prog, attr, uattr, uattr_size); + //if (err < 0) + // goto free_used_maps; prog = bpf_prog_select_runtime(prog, &err); if (err < 0)

Slide 34

Slide 34 text

…というわけにはいかなかった ● カーネル起動時に以下ログを出した後にハングする ● 調査結果 ○ HID(human interface device)ドライバはeBPFプログラムを持っている ■ 📝 ユーザプログラムだけでなくカーネルサブシステムも eBPFプログラムを動かせる ○ HIDドライバの初期化処理で、この eBPFプログラムをロード ○ ロード処理が期待通り動作せずにハング … calling hid_bpf_init …

Slide 35

Slide 35 text

なにがまずかったのか ● verifierはチェック結果をstruct bpf_verifier_envという構造体に保存 ○ この構造体を後でeBPF VMが使う ○ 例えばJITコンパイルのヒントに使う ● bpf_verify()を呼ばないようにするとbpf_verifier_envが適切に更新されない ● 後続処理が期待通り動作しなくなり、ハング

Slide 36

Slide 36 text

verifierのコードをざくっと調査した結果 ● 内部的には100個近くのチェック関数が存在する ○ 例: 範囲内のメモリをアクセスしているか、数値をポインタとして使っていないか ● eBPFバイナリを仮想的に実行する ○ 実行中に至るところでチェック関数を読んで、失敗すると異常終了 ○ 要所要所でstruct bpf_verifier_envを更新

Slide 37

Slide 37 text

ソリューション ● 全てのチェック関数のエラーを握り潰す ● 全てのエラーreturnをreturn 0にするという単純置換はダメ ○ エラー時にearly returnしているところは処理を継続させなければいけない ○ チェックに直接関係無いエラーはそのまま残しておかなければいけない ■ 例: データ構造内の要素を得る関数が返す -ENOENT(要素が存在しなかった ) ● 規模 ○ verifierの処理本体(kernel/verifier.go)は約25000行 ○ エラーを返しているのは 600箇所程度 ■ verify失敗によりエラーを返している箇所は全て改造

Slide 38

Slide 38 text

なんとなくできた ● 以下のような差分が235箇所 diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e500ae801ffe..511a4ef42138 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c … @@ -5057,14 +5057,12 @@ static int check_mem_region_access(struct bpf_verifier_env *env, u32 regno, reg->smin_value + off < 0)) { verbose(env, "R%d min value is negative, either use unsigned index or do a if (index >=0) check.\n", regno); - return -EACCES; } …

Slide 39

Slide 39 text

まだ命令数の制限が残っていた #define BPF_COMPLEXITY_LIMIT_INSNS 1000000 /* yes. 1M insns */ include/linux/bpf.h:

Slide 40

Slide 40 text

こうじゃ! -#define BPF_COMPLEXITY_LIMIT_INSNS 1000000 /* yes. 1M insns */ +#define BPF_COMPLEXITY_LIMIT_INSNS 5000000000000000

Slide 41

Slide 41 text

完成! $ uname -r 6.8.0-waruibpf+ 悪いぜ! waruiBPFくん

Slide 42

Slide 42 text

waruiBPFの実行例

Slide 43

Slide 43 text

実行するコードが長い場合は? #include #include … SEC("tracepoint/syscalls/sys_enter_execve") int trace_exec(struct sys_enter_execve_args *ctx) { volatile int dummy = 0; for (int i = 0; i < 1000000; i++) dummy++; bpf_printk("execve: %d\n", dummy); return 0; }

Slide 44

Slide 44 text

動いた! … $ sudo bpftool prog load test.o /sys/fs/bpf/waruibpf autoattach … sudo bpftool prog load test.o /sys/fs/bpf/waruibpf autoattach sudo cat /sys/kernel/debug/tracing/trace_pipe kubelet-2102 [001] ...21 147.172604: bpf_trace_printk: execve: 1000000 …

Slide 45

Slide 45 text

パケット長のチェックを省くと? #include "vmlinux.h" #include #include char LICENSE[] SEC("license") = "GPL"; SEC("xdp") int xdp_print_eth_proto(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; //if (data + sizeof(struct ethhdr) > data_end) // return XDP_PASS; struct ethhdr *eth = data; __u16 proto = bpf_ntohs(eth->h_proto); bpf_printk("XDP eth proto = 0x%x\n", proto); return XDP_PASS; }

Slide 46

Slide 46 text

これも動いた! … $ sudo ip link set dev eth0 xdp obj test.o sec xdp … -0 [002] ..s2. 290.277071: bpf_trace_printk: XDP eth proto = 0x800 …

Slide 47

Slide 47 text

実はこんなことも… SEC("tracepoint/syscalls/sys_enter_execve") int trace_exec(struct sys_enter_execve_args *ctx) { // Please set the output of `sudo grep “ D jiffies_64$” /proc/kallsyms` unsigned long jiffies_64 = (unsigned long)0xffffffffabc079c0; bpf_printk("execve: %lu\n", jiffies_64); return 0; } $ sudo grep " D jiffies_64$" /proc/kallsyms ffffffffabc079c0 D jiffies_64 このシステムでは1msに一回増える

Slide 48

Slide 48 text

できちゃいます … sudo bpftool prog load test.o /sys/fs/bpf/waruibpf autoattach sudo cat /sys/kernel/debug/tracing/trace_pipe … cpuUsage.sh-3371 [002] ...21 703.282295: bpf_trace_printk: execve: 4295370609 sed-3372 [000] ...21 703.283616: bpf_trace_printk: execve: 4295370610 cat-3373 [000] ...21 703.284819: bpf_trace_printk: execve: 4295370611 sleep-3374 [000] ...21 703.285651: bpf_trace_printk: execve: 4295370612 ls-3380 [002] ...21 704.180586: bpf_trace_printk: execve: 4295371507 sed-3382 [000] ...21 704.286887: bpf_trace_printk: execve: 4295371614 cat-3383 [000] ...21 704.288296: bpf_trace_printk: execve: 4295371615 …

Slide 49

Slide 49 text

まとめ

Slide 50

Slide 50 text

waruiBPFのpros,cons ● pros ○ verifierを気にせずにプロトタイプを作って動作確認したいときに役立つ ■ 試してないけどAyaを使ってRustでeBPFプログラムを書くときにも多分使える ● cons ○ 安全ではない 悪いぜ! waruiBPFくん

Slide 51

Slide 51 text

将来の展望 ● sysfsに適当なファイル(/sys/fs/bpf/waruibpfとか)を生やして動作を制御 ○ 機能全体の発動有無 ○ verifierの細かいチェックごとの有効 /無効 ● 不具合修正 ○ 例: カーネル本体の即値はアクセスできるがポインタを読み書きできない

Slide 52

Slide 52 text

この取り組みで得たもの ● くじけない心 ● 眼精疲労

Slide 53

Slide 53 text

● eBPF ○ eBPF公式サイト ■ https://ebpf.io/ ○ 入門eBPF ■ https://www.oreilly.co.jp/books/9784814400560/ ○ 詳解システムパフォーマンス ■ https://www.oreilly.co.jp/books/9784814400072/ ● waruiBPF ○ カーネルパッチとサンプルコード ■ https://github.com/satoru-takeuchi/ebpf-waruibpf 参考

Slide 54

Slide 54 text

終わり 普通にeBPFを使うほうがいいと思うぜ ! waruiBPFくん そうだね! eBPFくん