Ubuntu Weekly Recipe

第443回再起動なしにカーネルを更新する「Canonical Livepatch Service」

Ubuntu Weekly Topicsの2016年10月21日号でも伝えているように、再起動をすることなくカーネルを更新できる「Canonical Livepatch Service」が発表されました。今回はこのサービスの使い方と仕組みについて紹介します。

「Canonical Livepatch Service」とは

「Canonical Livepatch Service」は、カーネルを再起動することなくセキュリティフィックスを適用するサービスです。サービスそのものの概要はTopicsを参照してください。仕組みとしてはKernel 4.0で実装され、Ubuntu 16.04 LTSから有効化されたCONFIG_LIVEPATCHを使っています。

カーネルのLivepatch機能とサービス側の都合により、このサービスを利用するにはいくつかの制約が存在します。

  • Intel/AMDの64ビットアーキテクチャ(amd64)のみ
  • Ubuntu 16.04 LTSの公式カーネル(Linux 4.4)のみ
  • Ubuntuカーネルでいうところのgenericフレーバーとlow latencyフレーバーのみ
  • Ubuntu 16.04 LTSから標準でインストールされるようになったsnapdの2.15以降が必要
  • Canonical Livepatch Serviceサイト(https://livepatch.canonical.com:443)へのアクセス
  • Ubuntu Single Sign Onアカウント(Launchpadなどのアカウント)

snapdが必要なのは、Livepatch Serviceのクライアントをsnapパッケージとして提供しているためです。このクライアントは、デーモンとして常駐し必要に応じてサーバーからパッチモジュールをダウンロードした上で、カーネルに取り込みます。

Livepatchクライアントは、Canoncialの商用サポートサービスであるUbuntu Advantageの機能であり、システム管理ツールでもあるLandscapeの一部として提供されます。そのためクライアントのソースコードは公開されていません。しかしながら使用している仕組みそのものは、もともとのカーネルにも実装されているものですし、Livepatch Serviceから提供するカーネルモジュールのソースコード自体は公開されています。そのため、本サービスを使用しなくても個人のマシンで同等の機能を実現することは可能です[1]⁠。

またセキュリティパッチそのものは、Livepatch Serviceとは関係なく提供されます。従来通りsudo apt update && sudo apt upgradeの組み合わせで最新のカーネルがインストールされますので、再起動してそのカーネルに切り替えてください。Livepatch Serviceはあくまで、再起動することなくセキュリティパッチを適用できるというサービスです[2]⁠。

Livepatch Serviceを使ってみる

まずはLivepatch Serviceをそのまま使ってみましょう。3台までならUbuntu Advantageを購入しなくても利用できます。あらかじめUbuntu Livepatch Serviceのサイトから、利用トークンを入手しておいてください。

図1 Ubuntu userにチェックを入れて、Get your tokenをクリックする
画像
図2 トークンが表示されるので控えておく
画像

次にsnapコマンドを使ってクライアントをインストールします。

$ snap find livepatch
Name                 Version  Developer  Notes  Summary
canonical-livepatch  5        canonical  -      Canonical Livepatch Client
$ sudo snap install canonical-livepatch
(中略)
$ snap list
Name                 Version  Rev  Developer  Notes
canonical-livepatch  5        15   canonical  -
ubuntu-core          16.04.1  423  canonical  -

snapはUbuntu 16.04 LTSから標準でインストールされるようになった、新しいパッケージフォーマットであるsnapを管理するためのコマンドです。snapフォーマットは、必要なソフトウェアをすべてそのパッケージに固めて提供するタイプのフォーマットであり、強制アクセス制御とシステムから独立したファイルツリーによるセキュリティ機構や、最近では言語ごとに存在するパッケージングシステムを流用しやすいパッケージングツールなどが特徴です。同様のアプリケーション配布システムとしてはFlatpakAppImageが存在するものの、どちらかというとCoreOSやRHELのAtomicに近い仕組みを採用しています。

さて、snapでは最小限のルートファイルシステムをubuntu-coreとしてインストールします。個々のsnapパッケージはこのubuntu-coreの上にかぶせる形でルートファイルシステムを構築し、個々のコマンドを実行することになります。snapパッケージはすべて/snapディレクトリ以下に保存されます。

$ ls /snap/
bin  canonical-livepatch  ubuntu-core
$ ls /snap/canonical-livepatch/current/
canonical-livepatch   command-canonical-livepatchd.wrapper  etc   meta  usr
canonical-livepatchd  command-canonical-livepatch.wrapper   keys  sbin
$ ps -fe | grep livepatch
root      1545     1  0 20:05 ?          00:00 /snap/canonical-livepatch/15/canonical-livepatchd
shibata   1587  1217  0 20:05 pts/25     00:00 grep --color=auto livepatch

canonical-livepatchをインストールすると、canonical-livepatchdが立ち上がります。またcanonical-livepatchコマンドを使ってこのデーモンと通信することになります。ちなみに、どちらのコマンドもGo言語で作られているようです。

Livepatch Serviceの初期設定

Livepatch Serviceを使うためには、環境によってはあらかじめ初期設定を行う必要があります。たとえばプロキシ経由でアクセスする場合やセキュアブートを利用している場合がそれに該当します。

Livepatch Serviceでは「livepatch.canonical.com:443」へアクセスする必要があります。プロキシ経由で接続する必要がある場合は、環境にあわせて次のような/var/snap/canonical-livepatch/configファイルを作成してください。

http-proxy: "http://proxy.example.co.jp:8080"
https-proxy: "https://proxy.example.co.jp:8080"
no-proxy: "example.co.jp"

設定を行ったら、canonical-livepatchdサービスを再起動します。

$ sudo systemctl restart snap.canonical-livepatch.canonical-livepatchd.service

Livepatch Serviceではカーネルモジュールをダウンロードしてカーネルに取り込みます。セキュアブート環境においてはロードするカーネルモジュールも検証の対象になります。つまりこのモジュールの署名検証用の証明書をあらかじめ取り込んでおく必要があります。セキュアブート環境はまず、以下のコマンドを実行した上で、再起動してください。

$ sudo mokutil --import /snap/canonical-livepatch/current/keys/livepatch-kmod.x509

Livepatch Serviceを有効化する

一通りの設定が終わったら、canonical-livepatchコマンドで、Livepatch Serviceの機能を有効化しましょう。

$ sudo canonical-livepatch enable トークン
Successfully enabled device. Using machine token: マシントークン
$ canonical-livepatch status --verbose
client-version: "5"
machine-id: マシンID
machine-token: マシントークン
architecture: x86_64
cpu-model: Intel Xeon E3-12xx v2 (Ivy Bridge)
last-check: 2016-10-22T20:08:49.442817865+09:00
boot-time: 2016-10-22T19:43:15+09:00
uptime: 27m20s
status:
- kernel: 4.4.0-45.66-generic
  running: true
  livepatch:
    state: nothing-to-apply
    version: ""
    fixes: ""

statusサブコマンドを実行すると、現在の状態が表示されます。この記事を執筆している時点での最新カーネルパッケージである「4.4.0-45.66」には、緊急度の高い修正パッチは提供されていないため、⁠nothing-to-apply」となっています。そこで、一つ前のカーネルバージョンに変更して起動してみましょう。まず、古いカーネルパッケージをインストールした上で、再起動します。

$ sudo apt install linux-image-4.4.0-43-generic linux-image-extra-4.4.0-43-generic

再起動するときにGRUBの画面で「Advanced options for Ubuntu」を選択し、そこから「Ubuntu, with Linux 4.4.0-43-generic」を選択してください。これで「4.4.0-43.63」カーネルになるはずです。この状態で再びstatusサブコマンドを実行します。

$ uname -a
Linux ubuntu-desktop 4.4.0-43-generic #63-Ubuntu SMP Wed Oct 12 13:48:03 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
$ canonical-livepatch status --verbose
client-version: "5"
machine-id: マシンID
machine-token: マシントークン
architecture: x86_64
cpu-model: Intel Xeon E3-12xx v2 (Ivy Bridge)
last-check: 2016-10-22T20:30:04.652+09:00
boot-time: 2016-10-22T20:29:30+09:00
uptime: 45s
status:
- kernel: 4.4.0-43.63-generic
  running: true
  livepatch:
    state: applied
    version: "13.3"
    fixes: '* CVE-2016-5195 LP: #1633547'

どうやらCVE-2016-5195対応のパッチが適用されているようです。実際にlsmodを実行すると、Livepatchのモジュールをロードしていることがわかります。

$ lsmod | grep livepatch
kpatch_livepatch_Ubuntu_4_4_0_43_63_generic_13    16384  1

カーネルモジュールは/var/snap以下にダウンロードされます。

$ sudo ls /var/snap/canonical-livepatch/common/payload/
changelog  copyright  cvelist.yaml  livepatch_Ubuntu_4_4_0_63_generic_13.ko  meta

Dirty COW対応を検証する

CVE-2016-5195はいわゆるDirty COWとも呼ばれる問題で、COW(copy-on-write)の操作時に競合状態が発生し、本来書き込めないはずの領域に書き込めてしまうという問題です。すでにこの脆弱性に対する実証コードはいくつも公開されており、これを使えばローカル環境でも問題を再現できます。ためしに書き込み可能フラグを落としているはずのファイルに一般ユーザーが書き込めてしまうdirtyc0w.cを試してみましょう。

$ wget https://raw.githubusercontent.com/dirtycow/dirtycow.github.io/master/dirtyc0w.c
$ sudo -s
$ echo this is not a test | sudo tee foo
$ sudo chown root: foo
$ sudo chmod 0404 foo
$ ls -lah foo
-r-----r-- 1 root root 19 Oct 22 18:03 foo
$ cat foo
this is not a test
$ gcc -pthread dirtyc0w.c -o dirtyc0w
$ ./dirtyc0w foo m00000000000000000
mmap 7f3cf7423000

madvise 0

procselfmem 1800000000

$ cat foo
this is not a test

dirtyc0wコマンドは、指定したファイルをmmap()し2つのスレッドを立ち上げ、片方ではひたすら/proc/self/mem経由でmmap()したアドレスに書き込みを行い、もう片方はmadvice()mmap()したアドレスとMADV_DONTNEEDを渡し続けるというコードです。いずれも1億回同じ処理を繰り返すことで、どこかのタイミングで競合状態を発生させ、書き込みを成功させようとします。1億回繰り返すので、環境によってコマンドの完了までに相当時間がかかります。

脆弱性の存在するはずの4.4.0-43カーネルであるにも関わらず、Livepatch Serviceのカーネルモジュールをロードしているおかげで、fooファイルの内容は書き換えられていません。これがLivepatch Service導入前であれば、本来変更できないはずのfooの内容がdirtyc0wコマンドに渡したm00000000000000000になってしまいます。

Livepatch Serviceを無効化する

現在のところ、一度ロードしたLivepatchモジュールをアンロードする方法はありませんrmmodコマンドもエラーとなります。

$ sudo rmmod kpatch_livepatch_Ubuntu_4_4_0_43_63_generic_13
rmmod: ERROR: Module kpatch_livepatch_Ubuntu_4_4_0_43_63_generic_13 is in use

現時点で無効化する唯一の方法は、canonical-livepatchコマンドからCanonical Livepatchのサービスそのものを停止することです。

$ sudo canonical-livepatch disable
Successfully disabled device. Removed machine-token: マシントークン

この状態で再起動すると、次回からはLivepatchがロードされなくなります。クライアントのソースが公開されていないため確実なことはわかりませんが、これによりマシントークンが一個なくなり、再び3台のマシンでLivepatch Serviceを有効化できるものと思われます。

当然のことながらsudo apt update && sudo apt upgradeでカーネルパッケージを更新した上で再起動すれば、不要となったLivepatchモジュールはロードされなくなります。

自分でLivepatchモジュールをビルドしてみる

Livepatch機能そのものはオリジナルのカーネルに存在する機能です。このため、セキュリティ対応やCanonicalのLivepatchサービスとは関係なく、Livepatch対応のモジュールを作ることが可能です。

特定の関数をフックするモジュール

Livepatch機能はカーネルの関数トレーシング機能CONFIG_FTRACEを利用して実装しています。この機能を使うと、特定の関数の呼び出しをフックして別の関数を呼び出すことが可能です。カーネルのソースコードにはLivepatch機能のサンプルとして、fs/proc/cmdline.ccmdline_proc_show()をフックするlivepatch-sample.cが存在します。cmdline_proc_show()をフックすれば、/proc/cmdlineの出力結果を変更できます。

そこでこのサンプルコードをビルドしてみましょう。まずカーネルモジュールのビルドのために、カーネルヘッダーパッケージとカーネルビルドに必要なパッケージをインストールします[3]⁠。

$ sudo apt install linux-headers-`uname -r`
$ sudo apt build-dep linux
$ mkdir livepatch && cd $_
$ wget http://kernel.ubuntu.com/git/ubuntu/ubuntu-xenial.git/plain/samples/livepatch/livepatch-sample.c

モジュールビルド用にKbuildとMakefileを作りましょう。

(Kbuildの内容)
obj-m := livepatch-sample.o
(Makefileの内容)
KDIR ?= /lib/modules/`uname -r`/build

default:
    $(MAKE) -C $(KDIR) M=$$PWD

あとはmakeするだけです。

$ make
$ ls
Kbuild    Module.symvers  livepatch-sample.c   livepatch-sample.mod.c  livepatch-sample.o
Makefile  built-in.o      livepatch-sample.ko  livepatch-sample.mod.o  modules.order

ビルドしたlivepatch-sample.koをロードして、その前後の/proc/cmdlineの結果を比較してみましょう。

$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.4.0-43-generic.efi.signed (略)
$ sudo insmod livepatch-sample.ko
$ cat /proc/cmdline
this has been live patched
$ echo 0 | sudo tee /sys/kernel/livepatch/livepatch_sample/enable
$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.4.0-43-generic.efi.signed (略)

/proc/cmdlineの出力結果がlivepatch-sample.cで指定した「this has been live patched」に変わっていることがわかります。さらにsysfs経由で当該モジュールを無効化すれば、/proc/cmdlineの出力結果も元に戻っています[4]⁠。

差分ファイルからパッチモジュールを作る

前項では特定の関数を完全に置き換えるパッチモジュールを作成しました。しかしながら脆弱性対応という観点から言えば、既存の処理を少しだけ修正した形でモジュールを置き換えるほうが一般的です。つまり修正用の差分ファイルからパッチモジュールを作ると便利です。Red Hatが開発しているkpatchシステムは、カーネルに対するパッチファイルからパッチモジュールを作成する仕組みです。UbuntuではこのkpatchをUbuntuカーネルの流儀にあわせて変更を加えた上でパッケージングしています。

kpatchの一部であるkpatch-buildをインストールすれば、前述のDirty COW対応モジュールのようなパッチモジュールを簡単に作ることが可能です[5]⁠。Canonical Livepatch Serviceも、基本的にこの方法で作ったカーネルモジュールを配布しています[6]⁠。そこでLivepatch Serviceで使われているカーネルへのパッチのコードリポジトリを利用して、Dirty COW対応のパッチモジュールを作ってみましょう。

$ sudo apt build-dep linux
$ sudo apt install kpatch-build
$ git clone https://git.launchpad.net/~ubuntu-livepatch/+git/xenial-livepatches
$ cd xeinali-livepatches/Ubuntu-4.4.0-43.63
$ cp Ubuntu-4.4.0-43.63.diff livepatch-Ubuntu-4.4.0-43.63-generic_13.diff

kpatch-buildコマンドは内部でカーネルをビルドするため、前項と異なり今回はカーネルビルドに必要なパッケージをすべてインストールしておく必要があります。また本記事執筆時点での最新コミットであるcommit:fa0c02eb4では、上記の最後のコマンドのように差分ファイルの名前を変えないといけませんでした。

あとはmakeを実行すれば、カーネルのソースコードなどをダウンロードして、内部でkpatch-buildを実行してくれます。かなり時間がかかりますので、気長に待ちましょう。

$ make
dget -u https://launchpad.net/ubuntu/+archive/primary/+files/linux_4.4.0-43.63.dsc
(中略)
wget -q https://launchpad.net/ubuntu/+archive/primary/+files/linux-image-4.4.0-43-generic-dbgsym_4.4.0-43.63_amd64.ddeb
(中略)
+ rm -rf /tmp/tmp.NlCVnRvZeW/buildroot
+ unset KCFLAGS
echo "Build complete for 4.4.0-43.63 generic amd64 revision 13"
Build complete for 4.4.0-43.63 generic amd64 revision 13
$ ls
Makefile                 linux-image-4.4.0-43-generic-dbgsym_4.4.0-43.63_amd64.ddeb  livepatch-Ubuntu-4.4.0-43.63-generic_13.diff
Ubuntu-4.4.0-43.63.diff  linux_4.4.0-43.63.diff.gz                                   livepatch_Ubuntu_4_4_0_43_63_generic_13.ko
linux-4.4.0              linux_4.4.0-43.63.dsc                                       vmlinux
linux-4.4.0.config       linux_4.4.0.orig.tar.gz

上記のうちlivepatch_Ubuntu_4_4_0_43_63_generic_13.koが、パッチモジュールです。あとはこれをロードすれば、Livepatch Serviceの時と同様にDirty COWの問題を防ぐことができます。

おすすめ記事

記事・ニュース一覧