Linuxカーネルのライブパッチを使ってテスト用のstubを作る

この記事はLinux Advent Calendar 2016の1日目の記事です。 テストする時にstubって便利ですよね。最近はnodejsで仕事のコードを書いているのでsinon便利だなとか思ってます。じゃあ、Linuxカーネルでもstub作れたら便利だよねという思うわけです。そうすると、我々にはlivepatchという機能があります。 というわけで、Linuxカーネルのlivepatchを使ってstubを作れるようにしてみようと思ったのが今回の記事のネタです。

今回のコードはLinux 4.9.0-rc4で動かしています。 stub機能の前提としてlivepacthを使うのでstubのコードはlivepatch方式でカーネルモジュールとして作成します。どんな関数をどのようにstubするかはカーネルモジュールで決めるので、ここは使う人の自由となります。カーネル側はstubをするための機能を実装する形です。

差分はこのようになってます。bc33b0〜はLinux 4.9-rc4をリリースした時のコミットです。

masami@kerntest:~/linux-kernel (kstub=)$ git diff --stat bc33b0ca11e3df467777a4fa7639ba488c9d4911
 arch/s390/include/asm/livepatch.h |   5 ++++
 arch/x86/include/asm/livepatch.h  |   5 ++++
 include/linux/kstub.h             |  59 +++++++++++++++++++++++++++++++++++++++
 include/linux/livepatch.h         |   1 +
 init/main.c                       |   3 ++
 kernel/livepatch/core.c           |  20 +++++++++++++-
 lib/Kconfig.debug                 |   7 +++++
 lib/Makefile                      |   1 +
 lib/kstub.c                       | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 samples/kstub/Makefile            |  17 ++++++++++++
 samples/kstub/kstub_test.c        |  74 +++++++++++++++++++++++++++++++++++++++++++++++++
 11 files changed, 452 insertions(+), 1 deletion(-)

この差分の内訳としては、stub機能を実現するためのコードがだいたいlib/にあり、stubする・しないを判定する処理を付けたかったのでlivepatch側にそれの対応を入れています。あとsample/にサンプルコードを置いてます。

stubする・しないの判定は以前に書いたlinux kernelのlivepatchにライブパッチを適用する・しないの判断機能を入れて遊ぶ - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモからの流用です。

sampleディレクトリのコードをビルドして、例えばmoreコマンドの時にstubを適用するとこのようになります。

masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ sudo insmod ./kstub_test.ko target_name=more
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ cat /proc/version
Linux version 4.9.0-rc4-kstub+ (masami@kerntest) (gcc version 6.2.1 20160830 (GCC) ) #144 SMP Sat Nov 19 12:42:38 JST 2016
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ more /proc/version
linux version
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.9.0-rc4-kstub+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$ more /proc/cmdline
hello, world
masami@kerntest:~/linux-kernel/samples/kstub (kstub=)$

サンプルコードはこのようなコードです。stubの処理はlivepatchをラップしているのでlivepatchを直接使うよりはコードが短くなってると思います。

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kstub.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/sched.h>

MODULE_DESCRIPTION("kstub test module");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");

static char *target_name = NULL;
module_param(target_name, charp, S_IRUGO);
MODULE_PARM_DESC(target_name, "Target process name");

static struct kstub *kstub;

static int kstub_cmdline_proc_show(struct seq_file *m, void *v)
{
        seq_printf(m, "hello, world\n");
        return 0;
}

static int kstub_version_proc_show(struct seq_file *m, void *v)
{
        seq_printf(m, "linux version\n");
        return 0;
}

static bool kstub_need_patch_apply(void)
{
        return !strcmp(current->comm, target_name);
}

static struct kstub_stub_data stubs[] = {
        {
                .func_name = "cmdline_proc_show",
                .stub_func = kstub_cmdline_proc_show,
        },
        {
                .func_name = "version_proc_show",
                .stub_func = kstub_version_proc_show,
        },
        {}
};

static int kstub_test_init(void)
{
        if (!target_name) {
                pr_info("target name is required\n");
                return -EINVAL;
        }

        kstub = KSTUB_CREATE();
        if (IS_ERR(kstub))
                goto out;

        return kstub->ops.setup_stub(kstub, stubs, kstub_need_patch_apply);

out:
        return PTR_ERR(kstub);
}

static void kstub_test_cleanup(void)
{
        pr_info("%s\n", __func__);
}

module_init(kstub_test_init);
module_exit(kstub_test_cleanup);
MODULE_INFO(livepatch, "Y");

stub機能でstub(livepatchでパッチしてる関数)はdebugfsで見れるようにしています。start_kernel()で/sys/kernel/debugにkstubディレクトリを作成し、stub機能でstubをセットアップする時にカーネルモジュール名のディレクトリを作ってstubsというファイルでstubしている関数名を読めるようにしています。

[root@kerntest kstub]# cat /sys/kernel/debug/kstub/kstub_test/stubs
cmdline_proc_show
version_proc_show
[root@kerntest kstub]#

コードはgithubにあります。

github.com

差分はこちらからも見れます。

Comparing torvalds:master...masami256:kstub · torvalds/linux · GitHub

TODOとしてはエラー処理が適当を修正したいとか、stubをenable/disable切り替えたいとかありあす。

現段階での最新になる4.9.0-rc7でも動きます。

masami@kerntest:~/linux-kernel/samples/kstub (kstub>)$ cat /proc/version
Linux version 4.9.0-rc7-kstub+ (masami@kerntest) (gcc version 6.2.1 20160830 (GCC) ) #145 SMP Thu Dec 1 00:16:51 JST 2016
masami@kerntest:~/linux-kernel/samples/kstub (kstub>)$ more /proc/version
linux version

Happy Hacking 🍣🍣🍣