systemtapでlivepatchする

CVE-2018-14634(Integer overflow in Linux's create_elf_tables function)でRed Hatのbugzillaを見ていて軽減策としてsystemtapでlivepatch的なことをしていて面白いな〜って思ったのでめもです。

環境はFedora 28で、カーネルのバージョンは4.18.9-200です。systemtapを使っているのでkernel-debuginfoパッケージも入れてます。

utsname構造体のnodenameを書き換えてホスト名を変えてみたいと思います。まずは初期状態はこうです。kerntestがnodenameになります。

masami@kerntest:~$ uname -a                                                                                                                                                                                        
Linux kerntest 4.18.9-200.fc28.x86_64 #1 SMP Thu Sep 20 02:43:23 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

cのコードでuname(2)を実行するものを作ります。

#include <stdio.h>
#include <sys/utsname.h>

int main(int argc, char **argv)
{
        struct utsname utsname = { 0 };

        printf("call uname(2) to execute livepatch via systemtap\n");

        if (uname(&utsname)) {
                perror("uname");
                return -1;
        }

        printf("patched nodename: %s\n", utsname.nodename);
        return 0;
}

まずはこれを-oオプション無しにコンパイルしてa.outで実行した場合。

masami@kerntest:~$ ./a.out
call uname(2) to execute livepatch via systemtap
patched nodename: kerntest

なにも起きませんね。

次に以下のsystemtapスクリプトを実行します。 uname(2)で呼ばれる__do_sys_newuname()にprobeを設定しています。 このスクリプトの内容としてはuname(2)を実行したプロセス名がdo-livepatchならそのプロセスが所属するuts名前空間に設定されてるnodenameの値を書き換えてます。

#!/usr/bin/env stap


%{
#include <linux/sched.h>
#include <linux/string.h>
%}

function set_dummy_uname:long()
%{
        struct task_struct *p = current;
        if (strcmp(p->nsproxy->uts_ns->name.nodename, "livepatched")) {
                if (unlikely(!strcmp(p->comm, "do-livepatch"))) {
                        memset(p->nsproxy->uts_ns->name.nodename, 0, sizeof(p->nsproxy->uts_ns->name.nodename));
                        strcpy(p->nsproxy->uts_ns->name.nodename, "livepatched");
                }
        }

        STAP_RETURN(0);

%}

probe begin {
        printf("start\n")
}


probe kernel.function("__do_sys_newuname") {
        set_dummy_uname()
}

probe end {
        printf("Done\n")
}

上記のコードを実行します。

masami@kerntest:~$ sudo stap -v -g test.stp                                                                                                                                                                       
Pass 1: parsed user script and 474 library scripts using 157272virt/50300res/8740shr/41432data kb, in 90usr/20sys/106real ms.                                                                                     
Pass 2: analyzed script: 3 probes, 1 function, 1 embed, 0 globals using 211816virt/105600res/9628shr/95976data kb, in 750usr/30sys/777real ms.                                                                    
Pass 3: translated to C into "/tmp/stapmurOiN/stap_134d2448e08da1a0409648297476fac2_1549_src.c" using 211816virt/105912res/9940shr/95976data kb, in 10usr/0sys/6real ms.                                          
Pass 4: compiled C into "stap_134d2448e08da1a0409648297476fac2_1549.ko" in 1990usr/740sys/2506real ms.
Pass 5: starting run.
start

そして、cコードの方をdo-livepatchという名前のバイナリでコンパイルして実行するとnodenameが書き換えられます。これ以降はuname(2)は変更した名前が返るようになってます。

masami@kerntest:~$ ./do-livepatch 
call uname(2) to execute livepatch via systemtap
patched nodename: livepatched
masami@kerntest:~$ uname -a
Linux livepatched 4.18.9-200.fc28.x86_64 #1 SMP Thu Sep 20 02:43:23 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
masami@kerntest:~$ uname -n
livepatched

今まではすでにログインしてたのでbashプロンプトのほうはホスト名変わってませんが、新規に接続するとホスト名が新しい名前になってます。

masami@saga:~$ ssh kerntest 
Last login: Thu Sep 27 02:05:41 2018 from 192.168.122.1
masami@livepatched:~$ 

というわけで、systemtapでlivepatch的なことをする実験でした( ´ー`)フゥー...