この記事はSFC-RG Advent Calendar 2019の16日目の記事です. 今年も遅くなって申し訳ないです.
この記事では,KASLR
,kernel address space layout randomization
について記述する.
KASLR
は,カーネル領域における仮想アドレス空間のランダム化である.この基本的な仕組みは,カーネルに限らずASLR
という仕組みで実現されているため,まずは,ASLR
の目的や仕組みついて記述する.
ASLR
ASLR
に関しては,こちらのQiitaの記事にて概要が書かれている.
(ちなみにKASLR
についても触れられている)
ここで述べられているシチュエーションは,
不正な方法によってプログラムに特定の命令を実行させる、不正なデータを操作させるというものです。攻撃には、(当然ながら)攻撃に使う命令、あるいはデータのアドレスが必要になります。ASLRが無い環境においてはプログラムのコードやデータは固定されたアドレスにロードされるので、動かしているプログラムのバイナリがどんなものかわかっていれば、攻撃者が攻撃に使うコードやデータのアドレスを知るのは簡単です。
となっている.実行ファイル内のコードセグメントと呼ばれる領域にはプログラムの命令自身が格納されている.そのほかにもスタック領域やヒープ領域と呼ばれる領域も存在する.
ASLR
が無効な状態で実行可能ファイルを実行すると,これらの領域は常に決まった仮想アドレス空間にロードされる.
すると,仮想アドレスの推測が攻撃者にとって容易になってしまう.そこで登場したのが,ASLR
である.この機能を使用することで,スタック領域やヒープ領域は常にランダム化されるが,コードセグメントに関してはコンパイル時に時にgccオプションで-fpic
をつけることが必要となっている.
これらの条件を満たした状態でプログラムの実行を行うと,実行のたびにロードされる仮想アドレス空間が変動する,というのがASLR
である.
KASLR
上述のASLR
に対して,KASLR
は名前の通り,カーネルの実行時の仮想アドレス空間を,起動のたびにランダム化するためのものである.
こちらに関しても,先ほどの記事で概要が述べられている.
カーネルは,vmlinux
からbzImage
になる際,stripされシンボル情報がバイナリから削除されるため,外から仮想アドレスを知ることはできない.
そこで,カーネルモジュールなどからカーネルに実装されている関数を参照する際は,System.map
(/boot/System.map-$(uname -r)
)を参照し,その仮想アドレスを知る.
System.map
には,それぞれのシンボルに対する仮想アドレスが記述されているが,KASLR
においてはこれはただの目安となっている.つまり,実際の仮想アドレスは起動時にランダム化されていて,/proc/kallsyms
の値とSystem.map
の値は異なる.
先ほどの記事から例を示す.
sudo cat /boot/System.map-4.19.0-6-amd64 | grep "T printk" # ffffffff810e085e T printk
sudo cat /proc/kallsyms | grep "T printk" # ffffffffad0e085e T printk
このように,異なるのがわかる.
この機能を無効にするには,nokaslr
をカーネルコマンドラインに追加する.
具体的には,/etc/default/grub
のうち,GRUB_CMDLINE_LINUX=""
となっている行を,GRUB_CMDLINE_LINUX="nokaslr"
とする.
実装
KASLR
の実装は,x86アーキテクチャにおいては,arch/x86/mm/kaslr.cに実装されている.(執筆時点でv5.4.3
)
Randomization is done on PGD & P4D/PUD page table levels to increase possible addresses.
とあるように,ページテーブルレベルでランダム化を行う.Linuxのページング機構に関しては,以下の記事に書いた.
このファイルの主要な関数は,kernel_randomize_memory()
であるが,これは/arch/x86/kernel/setup.c
の中のsetup_arch()で呼ばれている.
- 5段階ページングが有効かどうかを確認
- メモリレイアウトが
vaddr_start / vaddr_end
変数と一致しているかどうかを確認
などの前処理を経てから最終的にここでランダム化をする.
この処理では,kernel_memory_region
に対して,
static __initdata struct kaslr_memory_region { unsigned long *base; unsigned long size_tb; } kaslr_regions[] = { { &page_offset_base, 0 }, { &vmalloc_base, 0 }, { &vmemmap_base, 0 }, };
下にあるようなコードで
/* * Select a random virtual address using the extra entropy * available. */ entropy = remain_entropy / (ARRAY_SIZE(kaslr_regions) - i); prandom_bytes_state(&rand_state, &rand, sizeof(rand)); entropy = (rand % (entropy + 1)) & PUD_MASK; vaddr += entropy; *kaslr_regions[i].base = vaddr;
page_offset_base
, vmalloc_base
, vmemmap_base
の値を上書きしている.
実際に,この関数のentropy
加算後の値をKASLR
の有効/無効で試してみたところ,
無効なマシンでは,page_offset_baseの値は,
page_offset_base: ffff888000000000
ソースで定義されているPAGE_OFFSETの値に等しいことが確認できるが,
有効なマシンでは,ffff9ee680000000
となったり,再起動後は,ffff9d1100000000
と変化していることが確認できた.
page_offset_base: ffff9ee680000000
page_offset_base: ffff9d1100000000