linux: livepatchコードリーディングめも1 〜 patch側のデータ設定とpatchの登録まで

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)

クラウドを支える技術 ―データセンターサイズのマシン設計法入門 (WEB+DB PRESS plus)

  • 作者: ルイス・アンドレ・バロッソ(Luiz André Barroso),ジミー・クライダラス(Jimmy Clidaras),ウルス・ヘルツル(Urs Holzle),Hisa Ando
  • 出版社/メーカー: 技術評論社
  • 発売日: 2014/09/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (1件) を見る