linux: livepatchコードリーディングめも2 〜 patchのenable

前回の記事(linux: livepatchコードリーディングめも1 〜 patch側のデータ設定とpatchの登録まで - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ)はpatchを登録するを見たので今回はpatchを有効にする部分を見てきます。

まず、livepatch-sample.cでpatchを有効にしている部分を確認すると、klp_enable_patch()がそれだとわかります。

 75         ret = klp_enable_patch(&patch);
 76         if (ret) {
 77                 WARN_ON(klp_unregister_patch(&patch));
 78                 return ret;
 79         }

では、これの処理をkernel/livepatch/core.cで見てみます。

584 int klp_enable_patch(struct klp_patch *patch)
585 {
586         int ret;
587 
588         mutex_lock(&klp_mutex);
589 
590         if (!klp_is_patch_registered(patch)) {
591                 ret = -EINVAL;
592                 goto err;
593         }
594 
595         ret = __klp_enable_patch(patch);
596 
597 err:
598         mutex_unlock(&klp_mutex);
599         return ret;
600 }

ここの処理はpatchが登録されているかを確認して、登録されているなら__klp_enable_patch()を呼んで実際にpatchを有効化していってます。

__klp_enable_patch()はこうなっています。

537 static int __klp_enable_patch(struct klp_patch *patch)
538 {
539         struct klp_object *obj;
540         int ret;
541 
542         if (WARN_ON(patch->state != KLP_DISABLED))
543                 return -EINVAL;
544 
545         /* enforce stacking: only the first disabled patch can be enabled */
546         if (patch->list.prev != &klp_patches &&
547             list_prev_entry(patch, list)->state == KLP_DISABLED)
548                 return -EBUSY;
549 
550         pr_notice_once("tainting kernel with TAINT_LIVEPATCH\n");
551         add_taint(TAINT_LIVEPATCH, LOCKDEP_STILL_OK);
552 
553         pr_notice("enabling patch '%s'\n", patch->mod->name);
554 
555         for (obj = patch->objs; obj->funcs; obj++) {
556                 klp_find_object_module(obj);
557 
558                 if (!klp_is_object_loaded(obj))
559                         continue;
560 
561                 ret = klp_enable_object(obj);
562                 if (ret)
563                         goto unregister;
564         }
565 
566         patch->state = KLP_ENABLED;
567 
568         return 0;
569 
570 unregister:
571         WARN_ON(__klp_disable_patch(patch));
572         return ret;
573 }

最初はエラーチェックで、次のpr_notice_onece()、pr_notice()でこのようなログがでます。dmesg or journalctl -b -kですね。

Apr 25 12:56:03 miko kernel: livepatch: tainting kernel with TAINT_LIVEPATCH
Apr 25 12:56:03 miko kernel: livepatch: enabling patch 'livepatch_sample'

その次からのループがこの関数のメイン処理ですね。ループは登録されている関数分周り、その関数が存在するのがカーネルモジュールならそのカーネルモジュールがロードされているかチェックします。 対象の関数がカーネル本体にある場合は、obj->nameがNULLなはずなのでそれをチェックします。 このチェックをやっているのはklp_find_object_module()klp_is_object_loaded()です。 そして、チェックが終わったらklp_enable_object()を呼びます。ループを正常に抜けると最後にpatch全体のステータスをKLP_ENABLEDにして完了ですね。

ではklp_enable_object()を見てみます。

454 static int klp_enable_object(struct klp_object *obj)
455 {
456         struct klp_func *func;
457         int ret;
458 
459         if (WARN_ON(obj->state != KLP_DISABLED))
460                 return -EINVAL;
461 
462         if (WARN_ON(!klp_is_object_loaded(obj)))
463                 return -EINVAL;
464 
465         for (func = obj->funcs; func->old_name; func++) {
466                 ret = klp_enable_func(func);
467                 if (ret)
468                         goto unregister;
469         }
470         obj->state = KLP_ENABLED;
471 
472         return 0;
473 
474 unregister:
475         WARN_ON(klp_disable_object(obj));
476         return ret;
477 }

最初のほうのチェックは飛ばして、struct klp_funcに登録されている関数分のループでklp_enable_func()を呼んでいきます。ループが無事に終了するとオブジェクトのステータスがKLP_ENABLEDになります。

klp_enable_func()ですが、ここでは久しぶりに処理らしい処理が出てきます。 先に書いておくとlivepatchの機能はftraceを使って実現していて、この関数ではpatch対象の古い関数にフィルターが登録されている場合にフィルター登録するということをしています。

実際こんな感じで、patchを当てる前は/sys/kernel/debug/tracing/enabled_functionsは何もありませんが、patchを当てるとcmdline_proc_show()にフィルターがセットされたことがわかります。

[root@miko masami]# cat /sys/kernel/debug/tracing/enabled_functions
[root@miko masami]# cd tst_mod/
[root@miko tst_mod]# insmod ./livepatch-sample.ko
[root@miko tst_mod]# cat /sys/kernel/debug/tracing/enabled_functions
cmdline_proc_show (1) R I ->ftrace_ops_list_func+0x0/0x180
[root@miko tst_mod]#

では関数を見ていきましょう。

377 static int klp_enable_func(struct klp_func *func)
378 {
379         struct klp_ops *ops;
380         int ret;
381 
382         if (WARN_ON(!func->old_addr))
383                 return -EINVAL;
384 
385         if (WARN_ON(func->state != KLP_DISABLED))
386                 return -EINVAL;
387 
388         ops = klp_find_ops(func->old_addr);
389         if (!ops) {
390                 ops = kzalloc(sizeof(*ops), GFP_KERNEL);
391                 if (!ops)
392                         return -ENOMEM;
393 
394                 ops->fops.func = klp_ftrace_handler;
395                 ops->fops.flags = FTRACE_OPS_FL_SAVE_REGS |
396                                   FTRACE_OPS_FL_DYNAMIC |
397                                   FTRACE_OPS_FL_IPMODIFY;
398 
399                 list_add(&ops->node, &klp_ops);
400 
401                 INIT_LIST_HEAD(&ops->func_stack);
402                 list_add_rcu(&func->stack_node, &ops->func_stack);
403 
404                 ret = ftrace_set_filter_ip(&ops->fops, func->old_addr, 0, 0);
405                 if (ret) {
406                         pr_err("failed to set ftrace filter for function '%s' (%d)\n",
407                                func->old_name, ret);
408                         goto err;
409                 }
410 
411                 ret = register_ftrace_function(&ops->fops);
412                 if (ret) {
413                         pr_err("failed to register ftrace handler for function '%s' (%d)\n",
414                                func->old_name, ret);
415                         ftrace_set_filter_ip(&ops->fops, func->old_addr, 1, 0);
416                         goto err;
417                 }
418 
419 
420         } else {
421                 list_add_rcu(&func->stack_node, &ops->func_stack);
422         }
423 
424         func->state = KLP_ENABLED;
425 
426         return 0;
427 
428 err:
429         list_del_rcu(&func->stack_node);
430         list_del(&ops->node);
431         kfree(ops);
432         return ret;
433 }

エラーチェックを飛ばすと最初にklp_find_ops()でpatch対象の関数にlivepatchがすでにフィルターをセットしたかを確認し、 見つかった場合はlist_add_rcu()でfuncをopsのリストに追加。ということで1つのopsに対して複数のfuncが登録できるようですね。これは3個のpatchを当てた時にfoobar()という関数に対して1回目のpatch、2回目のpatchってな具合にスタックしていくということだと思います。で、2回目のpatchを外したら1回目のpatchが動作するようにと。

では、opsが見つからなかった場合(初めて登録するときとか)はハンドラとかftraceのフラグを設定するstruct klp_opsのメモリを確保してデータを設定。 次にファイル内でstaticなリストの変数(klp_ops)に作成したopsを追加。そして、funcをopsのリストに追加。このfuncをリストに追加しているのはopsがあった場合と同様の処理です。
そして、ftrace_set_filter_ip()でpatchを当てる旧関数のアドレスにフィルターを設定します。この時に呼ばれるハンドラはklp_ftrace_handler()です。これは後で見ましょう。 フィルターをセットしたら次にregister_ftrace_function()でfopsを渡してftraceに登録します。ここまでが正常に終わるとfuncのステータスがKLP_ENABLEDになります。あとはreturnしていくだけなので順番としてはfunc -> objs -> patchという順番でKLP_ENABLEDが設定されていくわけですね。

ではftraceに設定したハンドラのklp_ftrace_handler()を見てみます。

317 static void notrace klp_ftrace_handler(unsigned long ip,
318                                        unsigned long parent_ip,
319                                        struct ftrace_ops *fops,
320                                        struct pt_regs *regs)
321 {
322         struct klp_ops *ops;
323         struct klp_func *func;
324 
325         ops = container_of(fops, struct klp_ops, fops);
326 
327         rcu_read_lock();
328         func = list_first_or_null_rcu(&ops->func_stack, struct klp_func,
329                                       stack_node);
330         if (WARN_ON_ONCE(!func))
331                 goto unlock;
332 
333         klp_arch_set_pc(regs, (unsigned long)func->new_func);
334 unlock:
335         rcu_read_unlock();
336 }

処理としては最初にlist_first_or_null_rcu()opsのfunc_stackにつながっている最初の要素を取得します。 そして、これにセットされている関数のアドレス(func->new_func)をklp_arch_set_pc()に渡します。

klp_arch_set_pc()はこのようになっています。引数のipは呼び出したい関数のアドレスなのでrip(x86_64なら)をにして、処理を新しい関数に移している感じですね。

 38 static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip)
 39 {
 40         regs->ip = ip;
 41 }

livepatchの処理としてはこのようになります。livepatchは既存の関数に対してftraceのフィルターを設定し、古い関数の実行時に設定したハンドラから新しい関数を実行するように動作させているということがわかりました。これによってlivepatch自体はpatchのデータの管理だけに注力できるというメリットがありますね。実際core.cも全部で1000行ちょいという短さですし。
あとは実際にこの機能を使った本当のbug fix patchがどのようになるのかは興味ありますね。4.0カーネル自体がまだ出たばっかりなので実運用環境向けのlivepatchが出てくるのはまだ先になるかもですが。

RaspberryPiで学ぶ ARMデバイスドライバープログラミング

RaspberryPiで学ぶ ARMデバイスドライバープログラミング