Out of memory and no killable processes... のカーネルパニックを起こしたい

イントロ

dsas.blog.klab.org

こちらのエントリを拝見したのだが Out of memory and no killable processes... というログを出してカーネルパニックするケースがあるのだなと初めて知った。

幸いにして、過去に同様のメッセージを出すカーネルパニックには遭遇したことがない。あるいはログが取れてなくて気がつかなかっただけかもね!

モチベーション

故意に異常を起こして、このカーネルパニックを見てみたい 🔥💀

Out of memory and no killable processes ... のコード

該当のログを出してカーネルパニックするコードは out_of_memory に書かれている (ソースは CentOS7.4 1708 )

/**
 * out_of_memory - kill the "best" process when we run out of memory
 * @zonelist: zonelist pointer
 * @gfp_mask: memory allocation flags
 * @order: amount of memory being requested as a power of 2
 * @nodemask: nodemask passed to page allocator
 * @force_kill: true if a task must be killed, even if others are exiting
 *
 * If we run out of memory, we have the choice between either
 * killing a random task (bad), letting the system crash (worse)
 * OR try to be smart about which process to kill. Note that we
 * don't have to be perfect here, we just have to be good.
 */
void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,
        int order, nodemask_t *nodemask, bool force_kill)
{


...

    p = select_bad_process(&points, totalpages, mpol_mask, force_kill);
    /* Found nothing?!?! Either we hang forever, or we panic. */
    if (!p) {
        dump_header(NULL, gfp_mask, order, NULL, mpol_mask);
        panic("Out of memory and no killable processes...\n"); 🔥💀
    }

select_bad_process で OOM Killer の対象となるタスクを選び出すことができないケースでパニックするのだと解釈した。 select_bad_process の中身を読みつつ、どういう状態を作り出せば このパニックを意図して起こせるのかな ... と思案

select_bad_process のコード
/*
 * Simple selection loop. We chose the process with the highest
 * number of 'points'.
 *
 * (not docbooked, we don't want this one cluttering up the manual)
 */
static struct task_struct *select_bad_process(unsigned int *ppoints,
        unsigned long totalpages, const nodemask_t *nodemask,
        bool force_kill)
{
    struct task_struct *g, *p;
    struct task_struct *chosen = NULL;
    unsigned long chosen_points = 0;

    rcu_read_lock();
    for_each_process_thread(g, p) {
        unsigned int points;

        switch (oom_scan_process_thread(p, totalpages, nodemask,
                        force_kill)) {
        case OOM_SCAN_SELECT:
            chosen = p;
            chosen_points = ULONG_MAX;
            /* fall through */
        case OOM_SCAN_CONTINUE:
            continue;
        case OOM_SCAN_ABORT:
            rcu_read_unlock();
            return ERR_PTR(-1UL);
        case OOM_SCAN_OK:
            break;
        };
        points = oom_badness(p, NULL, nodemask, totalpages);
        if (points > chosen_points) {
            chosen = p;
            chosen_points = points;
        }
    }
    if (chosen)
        get_task_struct(chosen);
    rcu_read_unlock();

    *ppoints = chosen_points * 1000 / totalpages;
    return chosen;
}

更に下の関数も読んだりしてみるが、うーん わからん

思案

とりあえずは OOM Killer が呼び出されるような状態を故意に作ったらよさそうだ。

とはいえ、適当なプロセスに無名ページ (Anon なページ) を大量に消費させても そのプロセスが OOM Killer で SIGKILL されてしまえば reclaim されるので、狙った通りの結果にはならなそう。プロセスで大量消費が駄目ならカーネルで何か異常なケースをつくったらよさそうで、バグったカーネルモジュールを書いてメモリリークを起こしたらいいだろうか? ... と考えていった

ところで、先のエントリでは IPVS: ip_vs_conn_new(): no memory というログも観測されている。そのログを出すコードを見ると以下の通り (注: カーネルのバージョンが明記されていないので、実装が全然違う可能性がある)

/*
 * Create a new connection entry and hash it into the ip_vs_conn_tab
 */
struct ip_vs_conn *
ip_vs_conn_new(const struct ip_vs_conn_param *p,
           const union nf_inet_addr *daddr, __be16 dport, unsigned int flags,
           struct ip_vs_dest *dest, __u32 fwmark)
{
    struct ip_vs_conn *cp;
    struct netns_ipvs *ipvs = net_ipvs(p->net);
    struct ip_vs_proto_data *pd = ip_vs_proto_data_get(p->net,
                               p->protocol);

    cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC); 🔥
    if (cp == NULL) {
        IP_VS_ERR_RL("%s(): no memory\n", __func__);
        return NULL;
    }

ここを端緒にしてカーネルモジュールをつくってみて試行錯誤した

検証環境

検証 VM の Vagrantfile

カーネルパニックを起こした際のコンソールログを ホスト(macOS)側で取りたいので、ちょいと設定を追加する

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--memory", "512", "--cpus", "4", "--ioapic", "on"]
    vb.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
    vb.customize ["modifyvm", :id, "--uartmode1", "file", "/tmp/vagrant-ttyS0.log"]
  end
end

設定は過去に自分で調べたものを流用した

d.hatena.ne.jp

バグったカーネルモジュールを書く

いろんなアプローチがあると思うんだけど、何も考えずに無限ループで slab を消費するコードを書いたらいいだろうかとやってみた

ちゃんとしたカーネルモジュールは作れないけど、バグったコードは書ける 😊

slabpanic.c
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/mm.h>

MODULE_AUTHOR("hiroya");
MODULE_DESCRIPTION("kmem_cache test");
MODULE_LICENSE("GPL");

struct foo_bar {
        unsigned long id;
};

static struct kmem_cache *cachep;

static int __init cachep_init(void)
{
        struct foo_bar *foo;

        cachep = kmem_cache_create("foo_bar",
                          sizeof(struct foo_bar),
                          0, SLAB_PANIC, NULL);
        if (!cachep)
                return -ENOMEM;

        for (;;) {
                foo = (struct foo_bar *)kmem_cache_alloc(cachep, GFP_KERNEL);
                if (!foo)
                        return -ENOMEM;
        }

        /* never */
        return 0;
}

module_init(cachep_init);

Unreclaimable な slab として計上されるので、memory pressure がかかっても reclaim されない (よね?)

Makefile
obj-m := slabpanic.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
  $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

insmod:
  insmod slabpanic.ko

rmmod:
  rmmod slabpanic.ko

reload:
   -make rmmod
  make insmod

検証手順

先のカーネルモジュールをコンパイルして insmod すると、カーネルモジュールの初期化フェーズでバグった処理が走りカーネルパニックを起こしてくれた。 ひどい

$ sudo yum install kernel-devel development-tool
$ make
$ sudo make insmod

手順を実行しつつ、ホストOS で tail -F /tmp/vagrant-ttyS0.log するとコンソールログを観察できるので ここで成否を確かめた。

言うまでもないが、実行したホストをカーネルパニックに追い込むのが目的のコードなので サンドボックスVM で実行してね

成功した例 (1)

ログを観察したところ 以下のような挙動だった

  • OOM Killer がプロセスを片っ端から SIGKILL で止めていく
  • Kernel panic - not syncing: Out of memory and no killable processes... で停止する
[  296.864258] Killed process 733 (dhclient) total-vm:113372kB, anon-rss:0kB, file-rss:4kB, shmem-rss:0kB
[  296.888354] Out of memory: Kill process 936 (tuned) score 5 or sacrifice child
[  296.901516] Killed process 936 (tuned) total-vm:562412kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  296.912110] Out of memory: Kill process 1009 (gmain) score 5 or sacrifice child
[  296.916282] Killed process 1009 (gmain) total-vm:562412kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  296.931245] Out of memory: Kill process 670 (rsyslogd) score 2 or sacrifice child
[  296.978328] Killed process 670 (rsyslogd) total-vm:208024kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  297.009166] Out of memory: Kill process 658 (polkitd) score 1 or sacrifice child
[  297.013594] Killed process 658 (polkitd) total-vm:534132kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  297.050075] Out of memory: Kill process 666 (gmain) score 1 or sacrifice child
[  297.056624] Killed process 666 (gmain) total-vm:534132kB, anon-rss:12kB, file-rss:0kB, shmem-rss:0kB
[  297.098530] Out of memory: Kill process 668 (NetworkManager) score 1 or sacrifice child
[  297.138628] Killed process 668 (NetworkManager) total-vm:760668kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  297.154869] Out of memory: Kill process 993 (newrelic-daemon) score 1 or sacrifice child

... 

[  303.985825] Kernel panic - not syncing: Out of memory and no killable processes... 🔥💀
[  303.985825] 
[  304.020770] CPU: 3 PID: 1 Comm: systemd Tainted: G           OE  ------------   3.10.0-693.2.2.el7.x86_64 #1
[  304.043044] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[  304.060127]  0000000000000000 00000000657f4ae1 ffff88001e37ba10 ffffffff816a3db1
[  304.082788]  ffff88001e37ba90 ffffffff8169dc74 ffffffff00000008 ffff88001e37baa0
[  304.094526]  ffff88001e37ba40 00000000657f4ae1 0000000000000000 0000000000000000
[  304.115307] Call Trace:
[  304.139868]  [<ffffffff816a3db1>] dump_stack+0x19/0x1b
[  304.149363]  [<ffffffff8169dc74>] panic+0xe8/0x20d
[  304.151684]  [<ffffffff81186c0a>] out_of_memory+0x4ea/0x4f0
[  304.158066]  [<ffffffff8169fcaa>] __alloc_pages_slowpath+0x5d6/0x724
[  304.163499]  [<ffffffff8118cd85>] __alloc_pages_nodemask+0x405/0x420
[  304.172175]  [<ffffffff811d4135>] alloc_pages_vma+0xb5/0x200
[  304.186998]  [<ffffffff81184f55>] ? filemap_fault+0x215/0x410
[  304.191294]  [<ffffffff811c453d>] read_swap_cache_async+0xed/0x160
[  304.197934]  [<ffffffff811f53a6>] ? mem_cgroup_update_page_stat+0x16/0x50
[  304.204454]  [<ffffffff811c4658>] swapin_readahead+0xa8/0x110
[  304.215250]  [<ffffffff811b235b>] handle_mm_fault+0xadb/0xfa0
[  304.233717]  [<ffffffff816afff4>] __do_page_fault+0x154/0x450
[  304.243727]  [<ffffffff816b0325>] do_page_fault+0x35/0x90
[  304.251919]  [<ffffffff816ac548>] page_fault+0x28/0x30
[  304.260941] Kernel Offset: disabled
  1. slab アロケータが無限ループで RAM を圧迫していく
  2. GFP フラグに GFP_KERNEL を指定しているので、 slab アロケータはプロセスのメモリも再利用するべく OOM Killer で SIGKILL していく
  3. SIGKILL していくと、終いに止められるプロセスがいなくなる
  4. もうだめだ〜 カーネルパニック🔥💀 ... !!!

と解釈したらよいのだろうか

成功した例 (2)

バックトレースを見てて気がついたが、直接 Buddy システムを呼び出す alloc_pages + GFP_KERNEL で RAM を使い切っても同様になるだろうか ... と試した結果

[  123.032286] Out of memory: Kill process 2580 (systemd-cgroups) score 0 or sacrifice child
[  123.054186] Killed process 2580 (systemd-cgroups) total-vm:316kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  123.080090] Out of memory: Kill process 2579 (systemd-cgroups) score 0 or sacrifice child
[  123.128085] Killed process 2579 (systemd-cgroups) total-vm:316kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  123.196090] Out of memory: Kill process 2581 (systemd-cgroups) score 0 or sacrifice child
[  123.224905] Killed process 2581 (systemd-cgroups) total-vm:316kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  123.277882] Out of memory: Kill process 654 (dbus-daemon) score 0 or sacrifice child
[  123.286584] Killed process 654 (dbus-daemon) total-vm:32772kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  123.309305] Kernel panic - not syncing: Out of memory and no killable processes... 🔥💀
[  123.309305] 
[  123.325140] CPU: 0 PID: 2 Comm: kthreadd Tainted: G           OE  ------------   3.10.0-693.2.2.el7.x86_64 #1
[  123.334332] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[  123.342585]  0000000000000000 000000007d1ab5fc ffff88001e3ebaa8 ffffffff816a3db1
[  123.346257]  ffff88001e3ebb28 ffffffff8169dc74 ffffffff00000008 ffff88001e3ebb38
[  123.417844]  ffff88001e3ebad8 000000007d1ab5fc 0000000000000001 0000000000000000
[  123.459435] Call Trace:
[  123.473261]  [<ffffffff816a3db1>] dump_stack+0x19/0x1b
[  123.482569]  [<ffffffff8169dc74>] panic+0xe8/0x20d
[  123.487786]  [<ffffffff81186c0a>] out_of_memory+0x4ea/0x4f0
[  123.493100]  [<ffffffff8169fcaa>] __alloc_pages_slowpath+0x5d6/0x724
[  123.509553]  [<ffffffff8118cd85>] __alloc_pages_nodemask+0x405/0x420
[  123.514444]  [<ffffffff810b08c0>] ? insert_kthread_work+0x40/0x40
[  123.517256]  [<ffffffff8108510d>] copy_process+0x1dd/0x1970
[  123.523563]  [<ffffffff81029557>] ? __switch_to+0xd7/0x510
[  123.535710]  [<ffffffff81086a51>] do_fork+0x91/0x320
[  123.541051]  [<ffffffff816a8fad>] ? __schedule+0x39d/0x8b0
[  123.554521]  [<ffffffff81086d06>] kernel_thread+0x26/0x30
[  123.568133]  [<ffffffff810b1341>] kthreadd+0x2c1/0x300
[  123.576059]  [<ffffffff810b1080>] ? kthread_create_on_cpu+0x60/0x60
[  123.584054]  [<ffffffff816b4f58>] ret_from_fork+0x58/0x90
[  123.593481]  [<ffffffff810b1080>] ? kthread_create_on_cpu+0x60/0x60
[  123.629920] Kernel Offset: disabled

意図したとおりのカーネルパニックを起こしてくれた 😊 コードは下記の通り

#include <linux/module.h>
#include <linux/mm.h>

MODULE_AUTHOR("hiroya");
MODULE_DESCRIPTION("alloc_pages test");
MODULE_LICENSE("GPL");

static int __init alloc_pages_init(void)
{
        struct page *page;
        for (;;) {
            page = alloc_pages(GFP_KERNEL, 0);
            if (!page)
                return -ENOMEM;
        }

        return 0;
}

module_init(alloc_pages_init);

メモリサブシステムを理解していれば slab か buddy かはアプローチのちょっとした違いだよね ... となるのだろうか。ここらを正確に把握できていない

期待した結果を返さなかった例 (1)

GFP_ATOMIC を指定した場合 アロケータがプロセスのメモリを奪い取ることはしないので、 OOM Killer とカーネルパニックを起こさず page allocation failure: order:*, mode: **** のコンソールログを出すだけになる

    foo = (struct foo_bar *)kmem_cache_alloc(cachep, GFP_ATOMIC);
[ 3811.804847] slabpanic: loading out-of-tree module taints kernel.
[ 3811.804867] slabpanic: module verification failed: signature and/or required key missing - tainting kernel
[ 3812.310668] insmod: page allocation failure: order:0, mode:0x200020 🔥
[ 3812.310674] CPU: 3 PID: 2977 Comm: insmod Tainted: G           OE  ------------   3.10.0-693.2.2.el7.x86_64 #1
[ 3812.310676] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[ 3812.310679]  0000000000200020 000000008add3229 ffff88000443f948 ffffffff816a3db1
[ 3812.310682]  ffff88000443f9d8 ffffffff81188810 0000000000000010 0000000000000000
[ 3812.310684]  ffffffffffffffff 0020002000000000 0000000000000001 000000008add3229
[ 3812.310687] Call Trace:
[ 3812.310698]  [<ffffffff816a3db1>] dump_stack+0x19/0x1b
[ 3812.310703]  [<ffffffff81188810>] warn_alloc_failed+0x110/0x180
[ 3812.310706]  [<ffffffff8169fd8a>] __alloc_pages_slowpath+0x6b6/0x724
[ 3812.310711]  [<ffffffff8118cd85>] __alloc_pages_nodemask+0x405/0x420
[ 3812.310714]  [<ffffffff811d1108>] alloc_pages_current+0x98/0x110
[ 3812.310717]  [<ffffffff811dbe8c>] new_slab+0x2fc/0x310
[ 3812.310720]  [<ffffffff811dd71c>] ___slab_alloc+0x3ac/0x4f0
[ 3812.310724]  [<ffffffffc01f903c>] ? cachep_init+0x3c/0x1000 [slabpanic]
[ 3812.310727]  [<ffffffffc01f903c>] ? cachep_init+0x3c/0x1000 [slabpanic]
[ 3812.310729]  [<ffffffff816a10ee>] __slab_alloc+0x40/0x5c
[ 3812.310731]  [<ffffffff811df6b3>] kmem_cache_alloc+0x193/0x1e0
[ 3812.310752]  [<ffffffffc01f9000>] ? 0xffffffffc01f8fff
[ 3812.310754]  [<ffffffffc01f903c>] cachep_init+0x3c/0x1000 [slabpanic]
[ 3812.310758]  [<ffffffff810020e8>] do_one_initcall+0xb8/0x230
[ 3812.310763]  [<ffffffff81100734>] load_module+0x1f64/0x29e0
[ 3812.310768]  [<ffffffff8134be00>] ? ddebug_proc_write+0xf0/0xf0
[ 3812.310770]  [<ffffffff810fcdd3>] ? copy_module_from_fd.isra.42+0x53/0x150
[ 3812.310772]  [<ffffffff81101366>] SyS_finit_module+0xa6/0xd0
[ 3812.310776]  [<ffffffff816b5009>] system_call_fastpath+0x16/0x1b
[ 3812.310777] Mem-Info:
[ 3812.310783] active_anon:7682 inactive_anon:7730 isolated_anon:0
 active_file:177 inactive_file:208 isolated_file:0
 unevictable:0 dirty:3 writeback:0 unstable:0
 slab_reclaimable:29127 slab_unreclaimable:72958
 mapped:557 shmem:317 pagetables:1229 bounce:0
 free:730 free_pcp:467 free_cma:0
[ 3812.310786] Node 0 DMA free:1916kB min:88kB low:108kB high:132kB active_anon:1208kB inactive_anon:1284kB active_file:16kB inactive_file:44kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writeback:0kB mapped:76kB shmem:144kB slab_reclaimable:3000kB slab_unreclaimable:7912kB kernel_stack:64kB pagetables:180kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
[ 3812.310791] lowmem_reserve[]: 0 470 470 470
[ 3812.310794] Node 0 DMA32 free:1004kB min:2728kB low:3408kB high:4092kB active_anon:29520kB inactive_anon:29636kB active_file:692kB inactive_file:788kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:507840kB managed:483848kB mlocked:0kB dirty:12kB writeback:0kB mapped:2152kB shmem:1124kB slab_reclaimable:113508kB slab_unreclaimable:283920kB kernel_stack:2368kB pagetables:4736kB unstable:0kB bounce:0kB free_pcp:1868kB local_pcp:348kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
[ 3812.310798] lowmem_reserve[]: 0 0 0 0
[ 3812.310800] Node 0 DMA: 81*4kB (UM) 75*8kB (UM) 38*16kB (UM) 8*32kB (UM) 2*64kB (U) 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 1916kB
[ 3812.310809] Node 0 DMA32: 258*4kB (M) 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 1032kB
[ 3812.310818] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB
[ 3812.310819] 757 total pagecache pages
[ 3812.310821] 75 pages in swap cache
[ 3812.310822] Swap cache stats: add 815, delete 740, find 0/0
[ 3812.310824] Free swap  = 1569600kB
[ 3812.310825] Total swap = 1572860kB
[ 3812.310827] SLUB: Unable to allocate memory on node -1 (gfp=0x20)
[ 3812.310828]   cache: kmalloc-8, object size: 8, buffer size: 8, default order: 0, min order: 0
[ 3812.310830]   node 0: slabs: 63204, objs: 32360448, free: 0

GFP フラグが何を指しているのか理解しないまま作業してたなぁとここで気がつく

期待した結果を返さなかった例 (2)

OOM Killer が pid = 1 のプロセスを選んでしまい Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000009 を出して カーネルパニックしたケースがあった

なんどかトライしたけど再現しなかったので、どうしてこうなったかよくわからなーい

[  153.205506] Killed process 700 (agetty) total-vm:110044kB, anon-rss:0kB, file-rss:4kB, shmem-rss:0kB
[  155.923906] Out of memory: Kill process 2569 (systemd) score 1 or sacrifice child
[  155.956264] Killed process 668 (dbus-daemon) total-vm:32768kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  156.031843] Out of memory: Kill process 2569 (systemd) score 1 or sacrifice child
[  156.038051] Killed process 1 (systemd) total-vm:193700kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[  156.069720] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000009 🔥
[  156.069720] 
[  156.088552] CPU: 0 PID: 2569 Comm: systemd Tainted: G           OE  ------------   3.10.0-693.2.2.el7.x86_64 #1
[  156.097784] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[  156.111842]  ffffffff81a32500 00000000b176e28b ffff8800001ebc58 ffffffff816a3db1
[  156.117057]  ffff8800001ebcd8 ffffffff8169dc74 ffffffff00000010 ffff8800001ebce8
[  156.131302]  ffff8800001ebc88 00000000b176e28b ffffffff81eb82c0 0000000000000009
[  156.138098] Call Trace:
[  156.140337]  [<ffffffff816a3db1>] dump_stack+0x19/0x1b
[  156.143527]  [<ffffffff8169dc74>] panic+0xe8/0x20d
[  156.147064]  [<ffffffff8108e166>] do_exit+0xa36/0xa40
[  156.156902]  [<ffffffff8113d4b5>] ? __delayacct_blkio_end+0x55/0x60
[  156.166918]  [<ffffffff816a8ba7>] ? io_schedule_timeout+0xe7/0x130
[  156.175137]  [<ffffffff8108e1ef>] do_group_exit+0x3f/0xa0
[  156.182938]  [<ffffffff8109e31e>] get_signal_to_deliver+0x1ce/0x5e0
[  156.194498]  [<ffffffff81184d32>] ? __lock_page_or_retry+0xb2/0xc0
[  156.200706]  [<ffffffff8102a467>] do_signal+0x57/0x6c0
[  156.204317]  [<ffffffff816afff4>] ? __do_page_fault+0x154/0x450
[  156.210010]  [<ffffffff8102ab2f>] do_notify_resume+0x5f/0xb0
[  156.218457]  [<ffffffff816ac33c>] retint_signal+0x48/0x8c
[  156.226365] Kernel Offset: disabled

カーネル内でこのカーネルパニックを起こすのは以下のようなコードだ

/*
 * When we die, we re-parent all our children, and try to:
 * 1. give them to another thread in our thread group, if such a member exists
 * 2. give it to the first ancestor process which prctl'd itself as a
 *    child_subreaper for its children (like a service manager)
 * 3. give it to the init process (PID 1) in our pid namespace
 */
static struct task_struct *find_new_reaper(struct task_struct *father)
        __releases(&tasklist_lock)
        __acquires(&tasklist_lock)
{
        struct pid_namespace *pid_ns = task_active_pid_ns(father);
        struct task_struct *thread;

        thread = father;
        while_each_thread(father, thread) {
                if (thread->flags & PF_EXITING)
                        continue;
                if (unlikely(pid_ns->child_reaper == father))
                        pid_ns->child_reaper = thread;
                return thread;
        }    

        if (unlikely(pid_ns->child_reaper == father)) {
                write_unlock_irq(&tasklist_lock);
                if (unlikely(pid_ns == &init_pid_ns)) {
                        panic("Attempted to kill init! exitcode=0xx\n", 🔥
                                father->signal->group_exit_code ?:
                                        father->exit_code);
                }    

適当にカーネルモジュールをがちゃがちゃ書き換えながら試していたので 再現がとれなくなったのはよくなかった

感想

  • とりあえず kmem_cache_alloc や alloc_pages で RAM を無駄遣いして期待通りのカーネルパニック・ログを出すことはできた。
  • 「OOM Killer = プロセスを止めて無名ページ を reclaim する」でもリカバリできない状態に達した場合に、発生するカーネルパニックだと検証を通じて理解した
  • アロケータ周りや GFP_*** フラグの理解が曖昧で いきあたりばったりで試した感じがあるので、ここらを正確に把握することが TODO として残る
  • できれば、ソースで十全に理解した上で カーネルの秘孔を突く 検証したい

参考

OOM Killer について調べなおすべく 熊猫さくらさんの セキュリティ・キャンプ全国大会2016 解析トラック 3D The OOM CTF 2016.8.11 を読み直しているが、これは読み応えがあってヘビーだ 😲