Linux-4.0のライブパッチ機能を試してみる

# Linux-4.0のライブパッチ機能を試してみる

先週リリースされたLinux-4.0カーネルには、ライブパッチというカーネルパッチ機構が搭載されています。

 * Linux 4.0 released
   * https://lkml.org/lkml/2015/4/12/178
   
 * Kernel4.0で注目をあつめるライブカーネルパッチ機能
   * http://news.mynavi.jp/news/2015/04/22/192/

今回はこのライブパッチ機能を試してみました。環境はCentOS-7.1で、minimalインストールの状態から作業しています。

## Linux4.0カーネルビルド

### カーネルビルドに必要なパッケージ

まずはカーネルビルドに必要なパッケージをインストールします。gcc,bc,perlは必須です(bcとか入れ忘れるとカーネルビルド途中でエラーになる)。この後のカーネルコンフィグは"make menuconfig"で行うため、ncurses-develをインストールしています。

```shell-session
$ sudo yum install -y gcc
$ sudo yum install -y ncurses-devel
$ sudo yum install -y bc
$ sudo yum install -y perl
```

( `CentOS-7 7.4.1708` 環境で `make defconfig` した場合のビルドでは以下のパッケージも必要になっていたので追記)

```
$ sudo yum install -y elfutils-libelf-devel openssl-devel
```

加えて、必須ではありませんがman-pagesをインストールしておきます。

```shell-session
$ sudo yum install -y man-pages.noarch
```

### カーネルソースコードの準備

次にLinux-4.0のカーネルソースコードを準備します。単にダウンロードして展開するだけです。

```shell-session
$ curl -O https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.0.tar.xz
$ sudo tar Jxvf linux-4.0.tar.xz -C /usr/src
$ cd /usr/src
$ sudo ln -s linux-4.0 linux
```

### カーネルコンフィグを作る

ここからカーネルコンフィグを作ります。いったん"make defconfig"でデフォルトのカーネルコンフィグを作成しますが、デフォルトではライブパッチ機能が無効になっているので、"make menuconfig"で必要なコンフィグを追加します。

```shell-session
$ cd /usr/src/linux
$ sudo make defconfig
$ sudo make menuconfig
```

ライブパッチ機能を有効にするため、LIVEPATCHを設定します。コンフィグの依存関係があるので、先に依存関係のコンフィグを有効(=y)にしておかないとライブパッチ機能の項目が出てこない点に注意してください。

![img003.png](https://qiita-image-store.s3.amazonaws.com/0/61137/f21dd579-a123-e31b-73f9-a736699cbb17.png)

make menuconfigの画面ではスラッシュ(/)でキーワードの検索ができます。以下のように、片方に検索用のmenu config画面、もう一方で実際のコンフィグ設定を行うようにすると便利です。

![img002.png](https://qiita-image-store.s3.amazonaws.com/0/61137/d1d13e99-8f54-122c-652e-9fb0214d3dc2.png)

また、SAMPLE_LIVEPATCHを有効にして、ライブパッチのサンプルも一緒にビルドするようにします。

![img004.png](https://qiita-image-store.s3.amazonaws.com/0/61137/abdff7d8-6063-4cfc-f68a-3743d5c3c16e.png)

もう一つ注意が必要な点として、XFS_FSを有効にする、というのがあります。CentOS-7系ではファイルシステムがxfsになっているので、これを設定し忘れるとOS起動時にPANICが発生します(デフォルトコンフィグではXFS_FSが無効になっている...)。

![img009.png](https://qiita-image-store.s3.amazonaws.com/0/61137/196ad57f-742f-f6d0-a6c0-c7089c596630.png)

デフォルトコンフィグからの追加箇所は以下になります。

```shell-session
$ diff -u .config.defconfig .config | grep '^\+.*=.*$'
+CONFIG_KALLSYMS_ALL=y
+CONFIG_KPROBES_ON_FTRACE=y
+CONFIG_LIVEPATCH=y
+CONFIG_XFS_FS=y
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_GRAPH_TRACER=y
+CONFIG_DYNAMIC_FTRACE=y
+CONFIG_DYNAMIC_FTRACE_WITH_REGS=y
+CONFIG_FTRACE_MCOUNT_RECORD=y
+CONFIG_SAMPLES=y
+CONFIG_SAMPLE_LIVEPATCH=m
+CONFIG_LIBCRC32C=y
```

上記の.configはGistに置いてあります。

 * furandon-pig / dot.config.linux-4.0_enable_livepatch
   * https://gist.github.com/furandon-pig/738c77e11b2216c1c83c

### カーネルのビルド

カーネルをビルドします(単にmakeするだけ)。

```shell-session
$ cd /usr/src/linux
$ sudo make -j 2
```

### カーネルのインストール

カーネルのビルドはmake一発なのですが、カーネルのインストールはカーネルモジュールをインストールしたりgrubを更新したりと少し煩雑です。

まずはカーネルモジュールのインストールとbzImageのコピー、mkinitrdによるinitramfsの生成を行います。

 * filesystems/ramfs-rootfs-initramfs.txt
   * http://archive.linux.or.jp/JF/JFdocs/kernel-docs-2.6/filesystems/ramfs-rootfs-initramfs.txt.html

```shell-session
$ sudo make modules_install
$ sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-4.0.x86_64
$ ls /lib/modules
3.10.0-229.el7.x86_64  4.0.0
$ sudo mkinitrd /boot/initramfs-4.0.x86_64.img 4.0.0
```

### grubにビルドしたカーネルのエントリを追加する

先述した手順を実施後、grub2-mkconfigを実行することで、自動的に追加されたLinux-4.0のカーネルがgrubのエントリに追加されます。が、その前にちょっとgrubのデフォルト設定を変更しておきます。

CentOSでは起動時にプログレスバーが表示されて起動時のメッセージは表示されません。これはカーネル周りの開発では不便であるため、/etc/default/grubを編集して起動時のメッセージを表示するようにします。

```shell-session
$ cd /etc/default
$ diff -u grub.old grub
--- grub.old    2015-04-21 02:43:58.254821294 +0900
+++ grub        2015-04-22 15:14:34.961342812 +0900
@@ -2,5 +2,5 @@
 GRUB_DEFAULT=saved
 GRUB_DISABLE_SUBMENU=true
 GRUB_TERMINAL_OUTPUT="console"
-GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet"
+GRUB_CMDLINE_LINUX="crashkernel=auto"
 GRUB_DISABLE_RECOVERY="true"
```

この状態でgrub2-mkconfigを実行すると、インストールしたLinux-4.0のエントリがgrubのメニューに追加されます。

```shell-session
$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg
```

## Linux-4.0カーネルで起動する

マシンを再起動してみます。grubのエントリに「Linux 4.0.x86_64」がありますね。

![img005.png](https://qiita-image-store.s3.amazonaws.com/0/61137/87920e32-5f70-91db-8b02-4536f161c976.png)

無事にLinux-4.0で起動できました。

![img006.png](https://qiita-image-store.s3.amazonaws.com/0/61137/ca404d72-1f57-7722-55da-2edde04f7a92.png)

## ライブパッチのサンプルを動かしてみる

さっそくライブパッチのサンプルを動かしてみます。サンプルはカーネルモジュールとしてビルドされているので、insmodでモジュールを読み込ませます。

このサンプルは/proc/cmdlineのハンドラ関数(fs/proc/cmdline.c:cmdline_proc_show())にパッチする例になっています。insmodの前後で"cat /proc/cmdline"が返す内容が変化しています。

![img007.png](https://qiita-image-store.s3.amazonaws.com/0/61137/014ad8e0-57c5-7d2d-d6f1-40390701ee71.png)

また、以下のように、ライブパッチを当てている状態で有効/無効を切り替えることができます。/proc/cmdlineの内容がライブパッチの前に戻せることがわかります。

![img008.png](https://qiita-image-store.s3.amazonaws.com/0/61137/4050d2d4-4a44-eccb-1368-ad3dbf2a315e.png)

### サンプルだけをビルドする

ライブパッチのサンプルプログラムは以下の手順でカーネルモジュールのみビルド可能です。先述した環境が構築されていれば、あとはサンプルプログラムで遊ぶことができます。

```shell-session
$ cd /usr/src/linux
$ sudo make V=1 samples/livepatch/livepatch-sample.ko
```

## まとめ

Linux-4.0のライブパッチ機能を試してみました。わかりやすいサンプルプログラムが付属しているのでいろいろと試してみると面白そうです。write()等のシステムコールにライブパッチを当ててみたいのですが、SYSCALL_DEFINE[0-6]で定義される関数(sys_write()とか)には一工夫(多分リンク周りの問題...)が必要そうです。また、fs/open.c:vfs_open()にパッチを当てるとinsmod後に刺さってしまったりするなど、パッチできる関数には何らかの制限がありそうです。

## 追記

ライブパッチ機能を利用するための環境構築手順が把握できたので、これを元にみんなでライブパッチを試してみる会を企画してみました。面白いライブパッチのアイディアがある方は是非ご参加ください。

 * Linux-4.0のライブパッチ機能を試してみる会(1)
   * http://connpass.com/event/14416/