Fedora 22でカーネルライブパッチを試してみる

Fedora 22でカーネルパッチを動かしてみます。

Fedora 22はCONFIG_HAVE_LIVEPATCHは有効ですが、CONFIG_LIVEPATCHは有効ではありません。

linux 4.0のカーネルをダウンロードしてきて、自分でビルドした方がよりシンプルかもしれませんが、Fedora 22のSRPMからカーネルを再構築して、Fedora 22にインストールします。


1 カーネルを再構築する

Fedora 22のSRPMを設定変更して利用します。


1.1 カーネルのビルド環境を整える

rpmbuildの環境を整えます。ホームディレクトリにrpmbuildディレクトリが作成されます。

$ sudo dnf install -y rpmdevtools
$ rpmdev-setuptree

カーネルのビルドに必要なパッケージを整えます。

$ sudo dnf builddep -y kernel
$ sudo dnf install -y numactl-devel pesign

Fedora 22のSRPMを取得してインストールします。

$ dnf download --source kernel
$ rpm -Uvh kernel-[Version].src.rpm

rpmbuildディレクトリにFedora 22カーネルのソースコードが展開されます。

$ tree ~/rpmbuild
rpmbuild
├── BUILD
├── RPMS
├── SOURCES
│   ├── 0001-ALSA-hda-realtek-Support-Dell-headset-mode-for-ALC28.patch
<snip>
│   └── xen-pciback-Don-t-disable-PCI_COMMAND-on-PCI-device-.patch
├── SPECS
│   └── kernel.spec
└── SRPMS

5 directories, 122 files

1.2 カーネルのコンフィグを変更

.configを変更する為一度ビルドします(時間かかります)。

$ cd ~/rpmbuild
$ rpmbuild -bb SPECS/kernel.spec

カーネルソースツリーに移動してmake menuconfigで.configを変更します。

$ cd BUILD/kernel-4.0.fc22/linux-4.0.4-301.fc22.x86_64/
$ make menuconfig

CONFIG_LIVEPATCHを有効にします。

Processor type and features  --->
  Kernel Live Patching

CONFIG_SAMPLE_LIVEPATCHを有効にします。

Kernel hacking  --->
  [*] Sample kernel code --->
    <M>   Build live patching sample -- loadable modules only

make menuconfigで.configをセーブして.configをSOURCESに保存します。

$ cp .config ~/rpmbuild/SOURCES/config-x86_64-generic

specファイルのカーネルバージョンを変更します。

$ diff -uprN ~/rpmbuild/SPECS/kernel.spec{.org,}
--- SPECS/kernel.spec.org       2015-05-30 00:49:19.661321057 +0900
+++ SPECS/kernel.spec   2015-05-30 00:47:03.705446416 +0900
@@ -40,7 +40,7 @@ Summary: The Linux kernel
 # For non-released -rc kernels, this will be appended after the rcX
 # and
 # gitX tags, so a 3 here would become part of release "0.rcX.gitX.3"
 #
-%global baserelease 301
+%global baserelease 301klp
 %global fedora_build %{baserelease}
 
 # base_sublevel is the kernel version we're starting with and patching

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

RPMを作成します(時間かかります)。

$ cd ~rpmbuild
$ rpmbuild -ba SPECS/kernel.spec

RPMをインストールして再起動します。

$ cd RPMS/x86_64
$ sudo rpm -i kernel-4.0.4-301klp.fc22.x86_64.rpm \
kernel-core-4.0.4-301klp.fc22.x86_64.rpm \
kernel-modules-4.0.4-301klp.fc22.x86_64.rpm 
$ sudo reboot

再起動後にカーネルのバージョンが更新されます。

$ uname -r
4.0.4-301klp.fc22.x86_64

カーネルライブパッチのコンフィグも有効になっています。

$ grep LIVEPATCH /boot/config-4.0.4-301klp.fc22.x86_64 
CONFIG_HAVE_LIVEPATCH=y
CONFIG_LIVEPATCH=y
CONFIG_SAMPLE_LIVEPATCH=m

1.4 ライブパッチのサンプルのビルド

上記のrpmbuildだけではカーネルライブパッチのサンプルがビルドされない為、カーネルのソースツリーでmakeを実行します。

$ cd ~/rpmbuild/BUILD/kernel-4.0.fc22/linux-4.0.4-301klp.fc22.x86_64/
$ make all
<snip>
  CC      samples/livepatch/livepatch-sample.mod.o
  LD [M]  samples/livepatch/livepatch-sample.ko
$ cp samples/livepatch/livepatch-sample.ko /tmp/  

2 ライブパッチのサンプル

CONFIG_SAMPLE_LIVEPATCHが有効な場合にビルドされるモジュールです。

2.1 initモジュール

klp_register_patch関数とklp_enable_patch関数を呼びます。

static int livepatch_init(void)
{
  int ret;

  ret = klp_register_patch(&patch);
  if (ret)
    return ret;
  ret = klp_enable_patch(&patch);
  if (ret) {
    WARN_ON(klp_unregister_patch(&patch));
    return ret;
  }
  return 0;
}

klp_register_patch関数はライブパッチを登録する関数です。

/sys/kernel/livepatch/<.mod>が見えるようになります。

.modはkpl_register_patch関数の引数に指定したstruct klp_patch構造体のメンバ変数modに指定した文字列です。

 

klp_enable_patch関数は登録したライブパッチを有効にする関数です。

内部で__klp_enable_patch関数を呼び出します。

__klp_enable_patch関数実行時にライブパッチが適用されたことになります。

 

/sys/kernel/livepatch/<.mod>/enableに1を指定すると__klp_enable_patch関数が呼ばれます。

よって、klp_enable_patch関数はklp_register_patch関数の直後に置く必要はありません。

 

2.2 各変数の内容

struct klp_patch構造体の変数patchは以下の通りです。.modに指定した文字列は/sys/kernel/livepatch/<.mod>に使われます。THIS_MODULEを指定した場合、このソースコードのモジュール名になります。

static struct klp_patch patch = {
  .mod = THIS_MODULE,
  .objs = objs,
};

struct klp_object構造体の変数objsは以下の通りです。

.nameはパッチを当てるモジュール名が指定されます(insmodされたモジュール)。

.nameが空の場合はカーネル本体のvmlinuxになります。

static struct klp_object objs[] = {
  {
    /* name being NULL means vmlinux */
    .funcs = funcs,
  }, { }
};

struct klp_func構造体の変数funcsは以下の通りです。

ここでcmdline_proc_show関数をlivepatch_cmdline_proc_show関数に置き換えます。

cmdline_proc_show関数は/proc/cmdlineをreadした際に呼び出される関数で、カーネル起動オプションが表示されます。

static struct klp_func funcs[] = {
  {
    .old_name = "cmdline_proc_show",
    .new_func = livepatch_cmdline_proc_show,
  }, { }
};

livepatch_cmdline_proc_show関数は以下の通りです。

カーネル起動オプションの代わりに"this has been live patched"と表示されるようになっています。

static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
{
  seq_printf(m, "%s\n", "this has been live patched");
  return 0;
}

2.3 実行結果

/sys/kernelへの書き込みはrootで実行します(sudoはダメかも)。

livepatch_sampleが有効な場合に/proc/cmdlineの内容が変わります。

# cat /proc/cmdline 
BOOT_IMAGE=/vmlinuz-4.0.4-301klp.fc22.x86_64
root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/swap
rd.lvm.lv=fedora/root rhgb quiet LANG=ja_JP.UTF-8
# insmod /tmp/livepatch-sample.ko 
# cat /proc/cmdline 
this has been live patched
# cat /sys/kernel/livepatch/livepatch_sample/enabled 
1
# echo 0 > /sys/kernel/livepatch/livepatch_sample/enabled 
# cat /proc/cmdline 
BOOT_IMAGE=/vmlinuz-4.0.4-301klp.fc22.x86_64
root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/swap
rd.lvm.lv=fedora/root rhgb quiet LANG=ja_JP.UTF-8

アンロードしようとするとエラーになります。

# rmmod livepatch-sample.ko
rmmod: ERROR: Module livepatch_sample is in use

3 ローダブルモジュールにカーネルライブパッチを適用

カーネルライブパッチのサンプルはカーネル本体に対するパッチであったので、ローダブルモジュールに対するカーネルライブパッチを試してみます。

ライブパッチを当てずとも、ローダブルモジュールを構築後にリロードすればいいだけの話で、相当に用途は限られるとは思います。


パッチを受ける側のモジュールがpatched、パッチを実行する側のモジュールがpatchです。

 

3.1 パッチを受ける側のモジュール

/proc/patchedを作成するモジュールです。

/proc/patchedをreadすると"Not patched yet."と表示されます。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int patched_show(struct seq_file *m, void *v)
{
  seq_printf(m, "%s\n", "Not patched yet.");
  return 0;
}

static int patched_open(struct inode *inode, struct file *file)
{
  return single_open(file, patched_show, NULL);
}

static const struct file_operations patched_fops = {
  .open = patched_open,
  .read = seq_read,
  .llseek = seq_lseek,
  .release = single_release,
};

static int patched_init(void)
{
  proc_create("patched", 0, NULL, &patched_fops);
  return 0;
}

static void patched_exit(void)
{
  remove_proc_entry("patched", NULL);
}

module_init(patched_init);
module_exit(patched_exit);

3.2 カーネルライブパッチを実行するモジュール

patched_show関数をpatch_show関数に置き換えるモジュールです。

__klp_enable_patch関数実行後に/proc/patchedをreadすると"Patched."と表示されます。

struct klp_object構造体の.nameにカーネルパッチを適用するモジュール名patchedを設定しています。

 

カーネルライブパッチのインターフェースはEXPORT_SYMBOL_GPLで公開されている為、カーネルライブパッチを実行するモジュールはGPLライセンスにする必要があります(MODULE_LICENSE("GPL")が必要)。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/livepatch.h>
#include <linux/seq_file.h>

static int patch_show(struct seq_file *m, void *v)
{
  seq_printf(m, "%s\n", "Patched.");
  return 0;
}

static struct klp_func funcs[] = {
  {
    .old_name = "patched_show",
    .new_func = patch_show,
  }, {}
};

static struct klp_object objs[] = {
  {
    .name = "patched",
    .funcs = funcs,
  }, {}
};

static struct klp_patch patch = {
  .mod = THIS_MODULE,
  .objs = objs,
};

static int patch_init(void)
{
  return klp_register_patch(&patch);
}

static void patch_exit(void)
{
  klp_disable_patch(&patch);
  klp_unregister_patch(&patch);
}

module_init(patch_init);
module_exit(patch_exit);
MODULE_LICENSE("GPL");

3.3 ビルド方法

KERNEL_DIR変数にビルド済みのカーネルディレクトリを指定してmakeを実行してください。

$ tar zxvf livepatch.tar.gz 
livepatch/
livepatch/patch/
livepatch/patch/patch.c
livepatch/patch/Makefile
livepatch/patched/
livepatch/patched/Makefile
livepatch/patched/patched.c
livepatch/Makefile
$ cd livepatch/
$ KERNEL_DIR=${HOME}/rpmbuild/BUILD/kernel-4.0.fc22/linux-4.0.4-301klp.fc22.x86_64/ make

3.4 実行結果

ローダブルモジュールにカーネルライブパッチを適用できています。

staticな関数でも最適化されてinline展開されていなければ置き換えられるっぽいですね。

# insmod patched/patched.ko 
# insmod patch/patch.ko 
# cat /proc/patched 
Not patched yet.
# cat /sys/kernel/livepatch/patch/enabled 
0
# echo 1 > /sys/kernel/livepatch/patch/enabled 
# cat /sys/kernel/livepatch/patch/enabled 
1
# cat /proc/patched 
Patched.
# echo 0 >
/sys/kernel/livepatch/patch/enabled 
# cat /sys/kernel/livepatch/patch/enabled 
0
# cat /proc/patched 
Not patched yet.
# rmmod patch
rmmod: ERROR: Module patch is in use
# rmmod patched
# rmmod patch
rmmod: ERROR: Module patch is in use

カーネルライブパッチモジュールのアンロードがやはりできません。


4 カーネルライブパッチのアンロードは現在サポートされていない

LWNのアーカイブに現在はサポートされていないと記載されていますね。

The core holds a reference on any kernel module that is patched to ensure it
does not unload while we are redirecting calls from it.  Also, the core takes a
reference on the patch module itself to keep it from unloading.  This is
because, without a mechanism to ensure that no thread is currently executing in
the patched function, we can not determine whether it is safe to unload the
patch module. For this reason, unloading patch modules is currently not
allowed.
ダウンロード
カーネルライブパッチを実行するローダブルモジュール
パッチを受ける側がpatched、パッチを実行する側がpatch。
livepatch.tar.gz
GNU tar 956 Bytes