linux kernelのlivepatchにライブパッチを適用する・しないの判断機能を入れて遊ぶ

2016年7月13日〜15日で行われたLinuxCon Japan 2016のセッションでカーネルハックの一環でカーネルのlivepatchの機能を使って、FreeBSDバイナリをLinuxで動かすってセッションに参加して、そういう使い方もありだなと思ったのでちょっと試してみました。アリだなと思ったところは条件に応じてライブパッチするしないの判断をできるようにすれば便利だなってとこだったので、そのへんを試してみた感じです。

まず、linux kernelのlivepatch機能を条件判断できるように変更します。

github.com

livepatch機能の変更点はpatch対象の関数が呼ばれた時のハンドラ関数のklp_ftrace_handler()です。ここで、条件判断の関数を呼んで、その関数がtrueを返したら新しい関数を呼び、falseの場合はオリジナルの関数を引き続き実行できるようにしています。

-    klp_arch_set_pc(regs, (unsigned long)func->new_func);
+   if (likely(klp_need_patch_apply_func())) 
+       klp_arch_set_pc(regs, (unsigned long)func->new_func);
+   else
+       klp_arch_enter_orig_func(regs, ip);
+

条件判断の関数はカーネル本体にはデフォルトの関数だけ作っていて、ライブパッチの登録時に変更するようにしました。Linux Kernelのlivepatchはカーネルモジュールとして作るので、条件判断の関数もlivepatch用のカーネルモジュール内で作ります。

livepatch test module

上のコードは、以下の3パターンの条件判断のうちどれかを選択できるようにしてます。

  1. 指定したPID
  2. 指定したプロセス名(comm)
  3. アクティブなPID名前空間が指定したPID名前空間に所属している

例えば、特定のPID名前空間のプロセスを対象にする場合、条件判断式は↓を使います。

static bool need_patch_apply_by_pidns(void)
{
        struct pid_namespace *pidns = task_active_pid_ns(current);
        return target_inum == pidns->ns.inum;
}

この場合、PID名前空間の指定はinode番号を使います。

実際の例でいくと、dockerコンテナ内のプロセスを対象にするとして、動かしているpid:1071のPID名前空間のinodeを調べます。

masami@kerntest:~$ sudo pstree -p
systemd(1)─┬─agetty(339)
           ├─agetty(340)
           ├─dbus-daemon(251)
           ├─dhcpcd(335)
           ├─docker(761)─┬─docker-containe(779)─┬─docker-containe(1027)─┬─bash(1043)───cmdline(1071)
           │             │                      │                       ├─{docker-containe}(1028)
           │             │                      │                       ├─{docker-containe}(1029)
           │             │                      │                       ├─{docker-containe}(1030)
           │             │                      │                       ├─{docker-containe}(1031)
           │             │                      │                       ├─{docker-containe}(1032)

pidがわかればそこで使っている名前空間のinode番号は/procを見ることでわかります。

masami@kerntest:~/livetest$ sudo ls -la /proc/1071/ns/pid
lrwxrwxrwx 1 root root 0 Jul 16 11:33 /proc/1071/ns/pid -> 'pid:[4026532216]'

コンテナで動かしているプロセスはこんな感じのやつです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void read_cmdline(void)
{
        int fd;
        char buf[256] = {0};

        fd = open("/proc/cmdline", O_RDONLY);
        if (fd == -1) {
                perror("open");
                exit(-1);
        }

        read(fd, buf, sizeof(buf) - 1);
        close(fd);
        printf("%s", buf);
}

int main(int argc, char **argv)
{

        printf("pid is %d\n", getpid());
        while (1) {
                read_cmdline();
                sleep(1);
        }
        return 0;

}

後はカーネルモジュールをinsmodするだけです。

masami@kerntest:~/livetest$ sudo insmod livetest.ko target_inum=4026532216

そうすると、dockerコンテナ内で動いているプロセスが最初は普通に/proc/cmdlineの初期値を読んでいたのに、livepatchモジュールの読み込みにより出力内容が変わります。

masami@kerntest:~/livetest$ cd && sudo docker run --rm -v `pwd`:/tmp/test -it base/archlinux /bin/bash
[root@e1d3894ff868 /]# cd /tmp/test
[root@e1d3894ff868 test]# ./cmdline
pid is 7
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
~~~略~~~
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
hello, world
hello, world
hello, world

dockerコンテナを抜けるとpid名前空間が変わるので、/proc/cmdlineを見ると、またカーネルコマンドラインが見れます。

^C
[root@e1d3894ff868 test]# exit
exit
masami@kerntest:~$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M

(´-`).oO(元々のlivepatch機能の意図からは外れるんだけど、ちょっとした実験とかには良さげな気もします

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ