Linuxカーネルもくもく会 #11でやったことです。Linux kernel 4.0 のlivepatchを調べてみる。patchとなるsamples/livepatch/にあるlivepatch-sample.cからスタートして読んでいきましょう。livepatchモジュールにpatchを登録するまでを見ていきます。
まずはpatchのデータ構造。構造体は3種類あって、これらで階層化構造になっている。
struct klp_func
struct klp_object
struct klp_patch
まずは最下層のstruct klp_funcから見てみます。構造体の定義はinclude/linu/livepatch.hにあって、見てみるとpatchのモジュールがセットする変数とlivepatch内部用の変数があります。old_addrはoptionalでシンボル名が重複している場合はアドレスをセットと言う感じですね。まずはサンプルコードを読みつつ行くのでexternal側だけ見ていきます。
46 struct klp_func { 47 /* external */ 48 const char *old_name; 49 void *new_func; 50 /* 51 * The old_addr field is optional and can be used to resolve 52 * duplicate symbol names in the vmlinux object. If this 53 * information is not present, the symbol is located by name 54 * with kallsyms. If the name is not unique and old_addr is 55 * not provided, the patch application fails as there is no 56 * way to resolve the ambiguity. 57 */ 58 unsigned long old_addr; 59 60 /* internal */ 61 struct kobject kobj; 62 enum klp_state state; 63 struct list_head stack_node; 64 };
実際に使っている方を見てみるとold_nameにpatch対象の関数名、new_funcに置き換える関数のアドレスを登録してますね。
49 static struct klp_func funcs[] = { 50 { 51 .old_name = "cmdline_proc_show", 52 .new_func = livepatch_cmdline_proc_show, 53 }, { } 54 };
変数名が複数形になっていることから分かる通り複数登録できます。
struct klp_objectもstruct klp_funcと同様にinternalとexternalの変数があります。 各変数についてはこの構造体に限らずコメントが付いているのでそちらを見ればOKかと思います。
95 struct klp_object { 96 /* external */ 97 const char *name; 98 struct klp_reloc *relocs; 99 struct klp_func *funcs; 100 101 /* internal */ 102 struct kobject *kobj; 103 struct module *mod; 104 enum klp_state state; 105 }; 106
サンプルコードの方ではfuncsにpatch対象の関数を設定したstruct klp_funcの配列を設定しているだけです。
56 static struct klp_object objs[] = { 57 { 58 /* name being NULL means vmlinux */ 59 .funcs = funcs, 60 }, { } 61 };
struct klp_patchも今までと同じくexternal、internalの変数があります。
115 struct klp_patch { 116 /* external */ 117 struct module *mod; 118 struct klp_object *objs; 119 120 /* internal */ 121 struct list_head list; 122 struct kobject kobj; 123 enum klp_state state; 124 };
サンプルではこのような設定になります。というか、基本的にこれ以外無いと思いますが。
63 static struct klp_patch patch = { 64 .mod = THIS_MODULE, 65 .objs = objs, 66 }; 67
ここまででの情報から見えてくるのはlivepathはfoo()にbugがあってこれを修正したい場合、livepatch用のカーネルモジュールを作成し、その中で修正を入れた関数を書く。そして、struct klp_funcにpatch対象の関数名と、bugを修正した関数のアドレスを設定する。そしてこれらのデータを受け取ったlivepatchのモジュールが新しい古い関数が呼ばれた時に新しい方を呼ぶような処理を入れるということでしょうか。システムコールのhookならシステムコールテーブルにあるアドレスを置き換えれば済むけど、そうなってないなら別途処理が必要でしょうから。
では、次にpatchの登録処理を見ていきます。livepatchの本体はkernel/livepatch/core.cです。patchの登録はklp_register_patch()で行います。関数はコメント入れても20行ちょいです。
889 int klp_register_patch(struct klp_patch *patch) 890 { 891 int ret; 892 893 if (!klp_initialized()) 894 return -ENODEV; 895 896 if (!patch || !patch->mod) 897 return -EINVAL; 898 899 /* 900 * A reference is taken on the patch module to prevent it from being 901 * unloaded. Right now, we don't allow patch modules to unload since 902 * there is currently no method to determine if a thread is still 903 * running in the patched code contained in the patch module once 904 * the ftrace registration is successful. 905 */ 906 if (!try_module_get(patch->mod)) 907 return -ENODEV; 908 909 ret = klp_init_patch(patch); 910 if (ret) 911 module_put(patch->mod); 912 913 return ret; 914 }
最初のklp_initialized()はlivepatchのモジュールが初期化されているかをチェックしています。初期化は通常通りmodule_init()で行われます。klp_init()が初期化の関数ですね。
module_init(klp_init)
次のは簡単な入力チェックです。
896 if (!patch || !patch->mod) 897 return -EINVAL;
try_module_get()は自分は初めて見ました。まあ、それはさておきこれはkernel moduleの参照数を1つ増やします。これはpatch->modにpatch対象の関数があるわけで、そうすると参照数を増やしておいて誰かにrmmodされてしまうのを防ぎたいということですね。
906 if (!try_module_get(patch->mod)) 907 return -ENODEV;
klp_register_patch()の最後の処理はklp_init_patch()です。見た感じ主要なのはklp_init_object()ですね。
811 static int klp_init_patch(struct klp_patch *patch) 812 { 813 struct klp_object *obj; 814 int ret; 815 816 if (!patch->objs) 817 return -EINVAL; 818 819 mutex_lock(&klp_mutex); 820 821 patch->state = KLP_DISABLED; 822 823 ret = kobject_init_and_add(&patch->kobj, &klp_ktype_patch, 824 klp_root_kobj, "%s", patch->mod->name); 825 if (ret) 826 goto unlock; 827 828 for (obj = patch->objs; obj->funcs; obj++) { 829 ret = klp_init_object(patch, obj); 830 if (ret) 831 goto free; 832 } 833 834 list_add_tail(&patch->list, &klp_patches); 835 836 mutex_unlock(&klp_mutex); 837 838 return 0; 839 840 free: 841 klp_free_objects_limited(patch, obj); 842 kobject_put(&patch->kobj); 843 unlock: 844 mutex_unlock(&klp_mutex); 845 return ret; 846 }
最初の方からざっと見ていくと、まず、patchの状態をKLP_DISABLEDにしてます。patchは当たっていないという状態ですかね。 次のif文はpatchのカーネルモジュールがobjsを設定しているかの確認。 次がkobject_init_and_add()でlivepatchをrootとしたkobjectの階層を作成。 サンプルのモジュールをinsmodしている状態でのディレクトリ構成はこんな感じになってました。
masami@miko:/sys/kernel/livepatch$ ls -R .: . .. livepatch_sample ./livepatch_sample: . .. enabled vmlinux ./livepatch_sample/vmlinux: . .. cmdline_proc_show ./livepatch_sample/vmlinux/cmdline_proc_show:
その次に設定されているstruct klp_func分のループでklp_init_object()を呼びます。ここは後で見ましょう。 最後に登録するpatchをリストに繋いで終了ですね。
では、klp_init_object()を見ていきます。 今までよりはちょっと長くなってます。
772 static int klp_init_object(struct klp_patch *patch, struct klp_object *obj) 773 { 774 struct klp_func *func; 775 int ret; 776 const char *name; 777 778 if (!obj->funcs) 779 return -EINVAL; 780 781 obj->state = KLP_DISABLED; 782 obj->mod = NULL; 783 784 klp_find_object_module(obj); 785 786 name = klp_is_module(obj) ? obj->name : "vmlinux"; 787 obj->kobj = kobject_create_and_add(name, &patch->kobj); 788 if (!obj->kobj) 789 return -ENOMEM; 790 791 for (func = obj->funcs; func->old_name; func++) { 792 ret = klp_init_func(obj, func); 793 if (ret) 794 goto free; 795 } 796 797 if (klp_is_object_loaded(obj)) { 798 ret = klp_init_object_loaded(patch, obj); 799 if (ret) 800 goto free; 801 } 802 803 return 0; 804 805 free: 806 klp_free_funcs_limited(obj, func); 807 kobject_put(obj->kobj); 808 return ret; 809 }
最初はpatchする関数が設定されているかのチェック。次ですが、先程はpatch全体の状態をKLP_DISABLEDにしてましたが、ここでは個々のstruct klp_objectをdisabledしてます。mod(struct module)はNULLで初期化。
次にklp_find_object_module()です。これは戻り値はなくて関数内部で値が設定されます。
処理としては、patch対象のstruct klp_objectのnameチェックして、設定されていなければvmlinuxが対象ということで何もせずにreturnします。サンプルはnameは何も設定してなかったですね。カーネルモジュールに対してpatchしたい場合はnameにモジュール名が入っているのでその名前を元にそのモジュールのstruct moduleオブジェクトを取得し、obj->modにセットします。
そして、klp_find_object_module()から帰ってきて対象がvmlinuxの場合はnameにvmlinuxを設定します。次は見たとおりで、先ほど見たようなkobjectの階層を作成ですね。
次はpatchする関数分のループでklp_init_func()を呼びます。ここは単純で、struct klp_funcにあるリストのstack_nodeを初期化、初期化しているstruct kpl_funcのstateをKLP_DISABLEDに設定、あとはkobject_init_and_add()でkobjectの作成です。
そうすると先程のsysfsのように./livepatch_sample/vmlinux/cmdline_proc_show:
という構造ができるのですね。
最後の処理はobjのnameがNULL(vmlinux)もしくはobjのmodがセットされているならklp_init_object_loaded()を呼びます。このような関数ですね。
750 /* parts of the initialization that is done only when the object is loaded */ 751 static int klp_init_object_loaded(struct klp_patch *patch, 752 struct klp_object *obj) 753 { 754 struct klp_func *func; 755 int ret; 756 757 if (obj->relocs) { 758 ret = klp_write_object_relocations(patch->mod, obj); 759 if (ret) 760 return ret; 761 } 762 763 for (func = obj->funcs; func->old_name; func++) { 764 ret = klp_find_verify_func_addr(obj, func); 765 if (ret) 766 return ret; 767 } 768 769 return 0; 770 }
最初はリロケーションの設定が有る場合。ここの処理はシンボルを探してklp_write_module_reloc()でそのモジュールにリロケーション情報を書き込むということをやっていると思います。
最後に呼んでいるのはklp_find_verify_func_addr()です。
231 static int klp_find_verify_func_addr(struct klp_object *obj, 232 struct klp_func *func) 233 { 234 int ret; 235 236 #if defined(CONFIG_RANDOMIZE_BASE) 237 /* KASLR is enabled, disregard old_addr from user */ 238 func->old_addr = 0; 239 #endif 240 241 if (!func->old_addr || klp_is_module(obj)) 242 ret = klp_find_object_symbol(obj->name, func->old_name, 243 &func->old_addr); 244 else 245 ret = klp_verify_vmlinux_symbol(func->old_name, 246 func->old_addr); 247 248 return ret; 249 }
処理としてはpatch対象の関数があるかどうかのチェックですね。
ここまででpatchの登録が終わりました。次はpatchのカーネルモジュール側でhttp://lxr.free-electrons.com/source/kernel/livepatch/core.c?v=4.0#L584を呼んでpatchを有効にします。
とりあえず今日はここまで ε-(;ーωーA フゥ
クラウドを支える技術 ―データセンターサイズのマシン設計法入門 (WEB+DB PRESS plus)
- 作者: ルイス・アンドレ・バロッソ(Luiz André Barroso),ジミー・クライダラス(Jimmy Clidaras),ウルス・ヘルツル(Urs Holzle),Hisa Ando
- 出版社/メーカー: 技術評論社
- 発売日: 2014/09/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る