Upgrade to Pro — share decks privately, control downloads, hide ads and more …

eBPFを使ったオレオレカーネル拡張入門

 eBPFを使ったオレオレカーネル拡張入門

Kernel/VM探検隊@東京 No16

Keisuke Nishimura

July 22, 2023
Tweet

More Decks by Keisuke Nishimura

Other Decks in Programming

Transcript

  1. オレオレ カーネル拡張 入門 Kernel/VM 探検隊 Tokyo #16 mumumu / 西村

    啓佑 (Keisuke Nishimura) ← Twitter: @mumumu_vm eBPFを使った 2023/07/22
  2. eBPF 基礎:概要 - カーネル内で動作する VM (In-Kernel VM) - 実行前に検証 =

    安全 - 基本的に 特定のイベントをフックして実行 - BPF_PROG_TYPE_XDP - BPF_PROG_TYPE_SK_SKB - BPF_PROG_TYPE_KPROBE - … 3 それぞれを Program Type と呼称
  3. eBPFでカーネルを拡張できるおもちゃ作ってみた Program Type: BPF_PROG_OREORE プログラム仕様: - eBPF: u32 を受け取って計算した答えを返却 -

    イベント: /sys/oreore/bpf への書き込み - 結果の出力: /sys/oreore/bpf の 読み込み - エラー処理やまともなVerificationは省略 11 ※Linux Kernel: 6.1.38 (最新のLTS)を使用
  4. eBPF の機能追加レシピ 1. 新しい eBPF Program Type を定義 2. アタッチする機能を用意

    3. プログラムを実行する関数定義 4. ユーザランドから呼び出し 13
  5. #1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 17

    BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: 追加したいProgram Type @/include/uapi/linux/bpf.h enum bpf_prog_type
  6. #1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 18

    BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: 予め作った次の2つの構造体: struct bpf_verifier_ops <このarg> ## _verifier_ops; struct bpf_prog_ops <このarg> ## _prog_ops; を参照する.(詳しくは後述)
  7. #1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 19

    BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: prog_ctx_type kern_ctx_type = eBPFからみた = カーネルからみた eBPFプログラムの引数の型 kern_ctx_type prog_ctx_type 変換
  8. #1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 20

    BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: prog_ctx_type kern_ctx_type = eBPFからみた = カーネルからみた eBPFプログラムの引数の型 kern_ctx_type prog_ctx_type 変換 今回は自動の変換で事足りるが, 複雑な場合 bpf_verifier_opsの convert_ctx_access() で変換
  9. #1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 22 bpf_verifier_ops 構造体の設定

    (とりあえずこの2つを登録) 引数addrから4 bytesのみ アクセス可能
  10. #1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 24

    1. enum bpf_prog_type にProg Type 追加 2. bpf_{prog|verifier}_ops 構造体 を定義 3. 適切な引数型で BPF_PROG_TYPE マクロ #1 まとめ:
  11. #2 アタッチする機能を用意 25 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する

    bpf システムコールに当該機能を追加 今回の例: ※ 本来 attach は fd に対して動作するが, 今回は簡単のため,fd 非依存にプログラムを適当なコンテキストに保存
  12. #2 アタッチする機能を用意 26 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する

    bpf システムコールに当該機能を追加 今回の例: enum bpf_attach_type (@/include/uapi/linux/bpf.h) に BPF_OREORE_ATTACH を追加 ※ 本来 attach は fd に対して動作するが, 今回は簡単のため,fd 非依存にプログラムを適当なコンテキストに保存
  13. #2 アタッチする機能を用意 27 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する

    bpf システムコールに当該機能を追加 今回の例: /kernel/bpf/syscall.c の以下の関数に追加 - bpf_prog_load_check_attach - attach_type_to_prog_type - bpf_prog_at(de)tach
  14. #2 アタッチする機能を用意 28 ゴール:新しいProgram Type に対応するフラグを設定 bpf_prog_load_check_attach(...) ... + case

    BPF_PROG_TYPE_OREORE: + if (expected_attach_type == BPF_OREORE_ATTACH) + return 0; + return -EINVAL; ... attach_type_to_prog_type(...) ... + case BPF_OREORE_ATTACH: + return BPF_PROG_TYPE_OREORE; ...
  15. #2 アタッチする機能を用意 29 ゴール:新しいProgram Type に対応するフラグを設定 +struct bpf_prog __rcu *oreore_prog;

    +EXPORT_SYMBOL_GPL(oreore_prog); +int oreore_bpf_prog_attach(struct bpf_prog *prog) +{ + rcu_assign_pointer(oreore_prog, prog); + return 0; +} ... bpf_prog_attach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_attach(prog); + break; ...
  16. #2 アタッチする機能を用意 30 ゴール:新しいProgram Type に対応するフラグを設定 +struct bpf_prog __rcu *oreore_prog;

    +EXPORT_SYMBOL_GPL(oreore_prog); +int oreore_bpf_prog_attach(struct bpf_prog *prog) +{ + rcu_assign_pointer(oreore_prog, prog); + return 0; +} ... bpf_prog_attach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_attach(prog); + break; ... この変数をLKM から参照し, progを実行する予定 (本来はfdからfilp を引いて 処理内容を追加)
  17. #2 アタッチする機能を用意 31 ゴール:新しいProgram Type に対応するフラグを設定 +int oreore_bpf_prog_detach() +{ +

    rcu_assign_pointer(oreore_prog, NULL); + return 0; +} ... bpf_prog_detach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_detach(); + break; ...
  18. #2 アタッチする機能を用意 32 ゴール:新しいProgram Type に対応するフラグを設定 1. enum bpf_attach_type にattach

    type を追加 2. bpf システムコールで使われる4つの関数に 1で追加したフラグの設定を追加 #2 まとめ: #1-2 の変更を反映済カーネルをコンパイル・実行
  19. #3 プログラムを実行する関数定義 33 ゴール: bpf_prog_run()の呼び出し (#1-2で作ったカーネル上で実行) LKMを書いて /sys/oreore/bpf への write

    から bpf_prog_run() を呼び出し 今回の例: u32 bpf_prog_run(const struct bpf_prog *prog, const void *ctx); • bpf_prog を実行し,その返り値を返却 • その際ctx(#1で定義したkern_ctx_type)が引数
  20. #4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 37 1. 入力(u32)に42を足すbpf 命令を錬成(気合い)

    2. libbpfの bpf_prog_load() でロード 3. libbpfのbpf_prog_attach() でアタッチ 今回の例:
  21. #4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 38 入力(u32)に42を足すbpf 命令を錬成(気合い) static

    struct bpf_insn insns[] = { // R0 = *(r1+0) BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0), // R0 += 42 BPF_ALU32_IMM(BPF_ADD, BPF_REG_0, 42), // Exit BPF_EXIT_INSN(), };
  22. #4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 39 入力(u32)に42を足すbpf 命令を錬成(気合い) static

    struct bpf_insn insns[] = { // R0 = *(r1+0) BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0), // R0 += 42 BPF_ALU32_IMM(BPF_ADD, BPF_REG_0, 42), // Exit BPF_EXIT_INSN(), }; 本来はtools/bpf/以下を編集し llvm でバイトコード生成 + libbpfのインフラを使用
  23. #4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 43 1. 対象 bpf

    プログラムを錬成 2. bpf_prog_{run|attach}()をコール #1-2 の変更を行ったカーネル上で実行 #4 まとめ:
  24. まとめ - eBPF = In-Kernel VM - eBPF を使ったカーネルの拡張が熱い! -

    意外と簡単にeBPFでカーネルを拡張可能! 46
  25. 47