Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

eBPFとwaruiBPF

 eBPFとwaruiBPF

kernel/vm探検隊@北陸 part8のプレゼン資料です。

https://kernelvm.connpass.com/event/367760/

Avatar for Satoru Takeuchi

Satoru Takeuchi PRO

December 06, 2025
Tweet

More Decks by Satoru Takeuchi

Other Decks in Technology

Transcript

  1. eBPF • Linuxカーネルの機能 • カーネル本体に手を入れず、カーネル空間で安全にプログラムを動かせる • うまくプログラムを書けば… ◦ ビルド済みバイナリを複数カーネルバージョンで動かせる ◦

    同、別アーキテクチャの CPUで動かせる • 適用範囲: ◦ トレース(例: bpftrace) ◦ パケット処理(例: Cilium) ◦ 不正動作検出&禁止(例: Falco)
  2. プログラムをカーネルにロードするまでの流れ ソースコード (eBPF C, Rust) eBPF bytecode (アーキテクチャ 非依存) eBPFサブシステム

    clang + LLVM BPF backend 変換 eBPFのツール、ライブラリ (libbpf, cilium agent) bpf() syscall呼び出しによりロード verifier(後述) eBPF bytecode 安全性チェック ユーザ空間 カーネル空間
  3. ロード後に実行する流れ eBPFサブシステム eBPF bytecode 何らかのイベント発生 ! カーネル 様々なサブシステム (1) 検出

    (2) イベントに対応した ebpfプログラム呼び出し eBPF VM (3) 実行 (JITコンパイル有り)
  4. プログラム実行時にメッセージを表示 #include <linux/bpf.h> #include <bpf/bpf_helpers.h> 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; }
  5. 実行すると… $ 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
  6. 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 ...
  7. ethernetフレーム受信時にL3のプロトコルを表示 #include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> 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; }
  8. eth0にアタッチすると… … $ sudo ip link set dev eth0 xdp

    obj test.o sec xdp $ sudo cat /sys/kernel/debug/tracing/trace_pipe
  9. protocolが表示された… … $ sudo ip link set dev eth0 xdp

    obj test.o sec xdp $ sudo cat /sys/kernel/debug/tracing/trace_pipe … <idle>-0 [002] ..s2. 1667.143565: bpf_trace_printk: XDP eth proto = 0x800 <idle>-0 [000] ..s2. 1667.159162: bpf_trace_printk: XDP eth proto = 0x800 <idle>-0 [002] ..s2. 1667.160285: bpf_trace_printk: XDP eth proto = 0x800 ... 0x800はIPv4
  10. 安全装置1: カーネル内のsandbox上での実行 BPF map 別のeBPFプログラム BPF map BPF map eBPFプログラム

    カーネル本体 eBPFヘルパー関数 読み書き 読み書き 発生したイベントのコ ンテキスト 呼び出し 読み書き(ほぼROM) データ、コード アクセス不可 アクセス
  11. 実行するコードが長いとき #include <linux/bpf.h> #include <bpf/bpf_helpers.h> … 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; }
  12. 検証失敗! … $ 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万命令を超えてはならない
  13. ethernetフレームだけ受信するだろ?とチェックを外すと #include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> 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; }
  14. 検証失敗! … $ 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バイト(イーサネット ヘッダのプロトコルを示すフィールドの末 尾)より短い可能性がある
  15. 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:
  16. 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)
  17. …というわけにはいかなかった • カーネル起動時に以下ログを出した後にハングする • 調査結果 ◦ HID(human interface device)ドライバはeBPFプログラムを持っている ▪

    📝 ユーザプログラムだけでなくカーネルサブシステムも eBPFプログラムを動かせる ◦ HIDドライバの初期化処理で、この eBPFプログラムをロード ◦ ロード処理が期待通り動作せずにハング … calling hid_bpf_init …
  18. なにがまずかったのか • verifierはチェック結果をstruct bpf_verifier_envという構造体に保存 ◦ この構造体を後でeBPF VMが使う ◦ 例えばJITコンパイルのヒントに使う •

    bpf_verify()を呼ばないようにするとbpf_verifier_envが適切に更新されない • 後続処理が期待通り動作しなくなり、ハング
  19. ソリューション • 全てのチェック関数のエラーを握り潰す • 全てのエラーreturnをreturn 0にするという単純置換はダメ ◦ エラー時にearly returnしているところは処理を継続させなければいけない ◦

    チェックに直接関係無いエラーはそのまま残しておかなければいけない ▪ 例: データ構造内の要素を得る関数が返す -ENOENT(要素が存在しなかった ) • 規模 ◦ verifierの処理本体(kernel/verifier.go)は約25000行 ◦ エラーを返しているのは 600箇所程度 ▪ verify失敗によりエラーを返している箇所は全て改造
  20. なんとなくできた • 以下のような差分が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; } …
  21. 実行するコードが長い場合は? #include <linux/bpf.h> #include <bpf/bpf_helpers.h> … 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; }
  22. 動いた! … $ 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 …
  23. パケット長のチェックを省くと? #include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> 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; }
  24. これも動いた! … $ sudo ip link set dev eth0 xdp

    obj test.o sec xdp … <idle>-0 [002] ..s2. 290.277071: bpf_trace_printk: XDP eth proto = 0x800 …
  25. 実はこんなことも… 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に一回増える
  26. できちゃいます … 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 …
  27. • 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 参考