カーネルスタックがvmalloc領域になったことの確認
Linux 4.9からカーネルスタックにvmalloc領域が用いられるようになった。
v3.10系とv4.10系で以下のモジュールをロードしてみて、実際に確認してみた。
ディストリはCentOS7.2
stack_test.c
#include <linux/module.h> #include <linux/kernel.h> static int __init stack_test_module_init( void ) { int sp; printk( KERN_INFO "int sp is @ %p\n", &sp ); return 0; } static void __exit stack_test_module_exit( void ) { } module_init( stack_test_module_init ); module_exit( stack_test_module_exit ); MODULE_DESCRIPTION( "stack_test" ); MODULE_LICENSE( "GPL2" );
obj-m := stack_test.o ROOTDIR := /usr/src/kernels/`uname -r`/ PWD := $(shell pwd) default: $(MAKE) -C $(ROOTDIR) M=$(PWD) modules clean: rm -f *.o *.ko
上記コードをmake
して、ロードした結果それぞれ以下のメッセージが/var/log/messages
に出力される。
v3.10系
Mar 30 21:00:24 localhost kernel: int sp is @ ffff88018c22fd4c
v4.10系
Mar 30 20:37:21 localhost kernel: int sp is @ ffffc90003607c7c
x86_64のメモリマップは、LinuxのソースのDocumentation/x86/x86_64/mm.txtによると
Virtual memory map with 4 level page tables: 0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm hole caused by [48:63] sign extension ffff800000000000 - ffff87ffffffffff (=43 bits) guard hole, reserved for hypervisor ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory ★v3.10系のスタックはここ ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space ★v4.10系のスタックはここ ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB) ... unused hole ... ffffec0000000000 - fffffc0000000000 (=44 bits) kasan shadow memory (16TB) ... unused hole ... ffffff0000000000 - ffffff7fffffffff (=39 bits) %esp fixup stacks ... unused hole ... ffffffef00000000 - ffffffff00000000 (=64 GB) EFI region mapping space ... unused hole ... ffffffff80000000 - ffffffffa0000000 (=512 MB) kernel text mapping, from phys 0 ffffffffa0000000 - ffffffffff5fffff (=1526 MB) module mapping space ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole
となり領域が異なることが分かる。
何がうれしいか?
v3.10系のカーネルスタックが置かれていた領域は開始アドレスの違いこそあれ、物理メモリがそのまま連続してマッピングされている。
この領域の良い点の1つは、メモリ割り当て/解放の速度が高速であることである。(仮想アドレスに対応する物理アドレスが予めわかっているため、ページテーブルの設定やTLBのフラッシュが必要ない)
一方で、物理的に連続したメモリを確保出来無い時はメモリ割り当てが失敗してしまうなどの欠点も存在する。
従来のカーネルでは、カーネルスタックとしてこの領域を用いており、各タスクに対し一定(最近は16KiBになったっぽい)のスタックを割り当てていた。
恐らく、カーネルの開発者はカーネルスタックを食い尽くさないように身長にコードを設計する必要があった。
というよりは、必要となるデータ構造のメモリ領域をスラブを用いてプールしておいたり、動的に確保することでスタックを食い尽すことを回避していたのだろう。
一方、v4.10系のカーネルスタックが置かれているvmalloc領域は、物理メモリが非連続的に仮想アドレスにマッピングされている。
非連続的なマッピングであるため、物理連続なメモリを確保できないからと言って、メモリ割り当てが失敗することはない。(基本的には)
しかし、割り当て/解放のたびにページテーブルをいじったりするので、vmalloc領域のメモリ確保/解放は遅い。(頻繁に呼ばれる処理で毎回この領域の獲得/解放を行うのはやめなければならない)
この領域をカーネルスタックに用いた場合、v3.10まで存在したスタックの限界というものが、かなり緩和されることとなる。 ※7/1 勘違いでした。
カーネル開発者は、スタックの枯渇に神経質になることもなく、また割り込みがネストしてしまいスタックを食い尽すという心配から解放されるようだ。
おわりに
思い立って10分ほどで記事を書いたので、大分大雑把な説明になってしまっている。
今後は、v4.9以降スタックオーバーフローが起きたときにどう回復しているのかなど、もう少し見ていきたい。
7/1 追記
Ubuntuではカーネルアドレス空間のレイアウトを少しいじっていることが分かった。 → カーネル空間のASLR的なものっぽい。
CentOS 7.2は上で示したレイアウトどおりっぽい
Jul 1 19:30:19 localhost kernel: vmalloc_start = ffffc90000000000 # VMALLOC_START Jul 1 19:30:19 localhost kernel: vmalloc_end = ffffe8ffffffffff # VMALLOC_END Jul 1 19:30:19 localhost kernel: page_offset = ffff880000000000 # PAGE_OFFSET ストレートマップの開始
Ubuntu 17.04は微妙に異なる。
Jul 1 19:27:49 ubuntu kernel: [22205.401374] vmalloc_start = ffffb58cc0000000 # VMALLOC_START Jul 1 19:27:49 ubuntu kernel: [22205.401375] vmalloc_end = ffffd58cbfffffff # VMALLOC_END Jul 1 19:27:49 ubuntu kernel: [22205.401376] page_offset = ffff892280000000 # PAGE_OFFSET ### 再起動後、変わっている。 Jul 1 20:08:18 ubuntu kernel: [ 45.508327] vmalloc_start = ffffb6a340000000 Jul 1 20:08:18 ubuntu kernel: [ 45.508328] vmalloc_end = ffffd6a33fffffff Jul 1 20:08:18 ubuntu kernel: [ 45.508328] page_offset = ffff9cc740000000
カーネルスタックのオーバーフローによる権限昇格とかが問題になることがしばしばあるので、ASLRで防ぐっていうのりですかね。 参考: