読者です 読者をやめる 読者になる 読者になる

Linux:面白そうなpatch(Address sanitizer for kernel)を見てみる

linux kernel

ふと見て面白そうだなと思った[RFC/PATCH RESEND -next 00/21] Address sanitizer for kernel (kasan) - dynamic memory error detector.、略してKASANがどんなものなのかを見てみる。どうしてもJ( 'ー`)しが思い浮かぶんだけどそこはスルー。
基本、見るのは00/21のメールで必要かなと思ったら実際のコードも確認するという形で。

現状ではこの辺を検出できる模様。
* Use after free bugs
* kmalloc()で確保した領域外に対する読み書き

まだ実現していな機能としてはこの辺が。
* Global buffer overflow
* Stack buffer overflow
* Use after return

この機能が使えるアーキテクチャx86x86_64、もしくはarm。メモリ管理の機能ではBuddy、SLUBをサポートしている。

基本的なコンセプトはシャドウメモリにアクセス対象のメモリの各バイトがアクセスしても安全かどうかを記録する。あと、コンパイラの機能を使って車道メモリをチェックしてメモリアクセスが安全か調べる。

low memory領域の1/8をシャドウメモリのために使用していて、アクセスしようとする対象のメモリアドレスとシャドウメモリのメモリマッピングに使う。 ここで言っているlow memoryが何かはパッチを見ないとわからなかったんだけど、この辺りのことを指しているのかな。

+void __init kasan_alloc_shadow(void)
+{
+   unsigned long lowmem_size = (unsigned long)high_memory - PAGE_OFFSET;
+   unsigned long shadow_size;
+   phys_addr_t shadow_phys_start;
+
+   shadow_size = lowmem_size >> KASAN_SHADOW_SCALE_SHIFT;
+
+   shadow_phys_start = memblock_alloc(shadow_size, PAGE_SIZE);

まず最初に出てくるhigh_memoryはx86_64の場合、下に貼ったarch/x86/kernel/setup.cにあるsetup_arch()の1067行目で決定している値だと思うわけです。なのでlowmemoryの領域はhigh_memoryより下のアドレス。

1054 #ifdef CONFIG_X86_32
1055         /* max_low_pfn get updated here */
1056         find_low_pfn_range();
1057 #else
1058         check_x2apic();
1059 
1060         /* How many end-of-memory variables you have, grandma! */
1061         /* need this before calling reserve_initrd */
1062         if (max_pfn > (1UL<<(32 - PAGE_SHIFT)))
1063                 max_low_pfn = e820_end_of_low_ram_pfn();
1064         else
1065                 max_low_pfn = max_pfn;
1066 
1067         high_memory = (void *)__va(max_pfn * PAGE_SIZE - 1) + 1;
1068 #endif

そして、KASAN_SHADOW_SCALE_SHIFTの値は3です。そしてshadow_sizeに値を代入しているとこで3bit右シフトして得たサイズがシャドウメモリとして使う領域(low memory領域の1/8)になるわけですね。

lowmemory領域の8バイトのひとかたまりをシャドウメモリの1バイトで表す。(余談だけど、メールにあるevery 8 bytes〜というところ、意味は十分に分かるんだけど日本語での表現は悩む。。)
書くシャドウメモリのバイトに対しては以下のエンコーディングが使われる。 0: 8バイトすべて問題なし
k(1-7):最初のkバイトはvadlidで以降はinvalid
負値:8バイトすべてがinvalid

負値にもいくつか意味が合って、なんでアクセスできないのかの意味が値によって異なる。mm/kasan/kasan.h参照。

不正なメモリアクセスを検出するためにスペシャルなコンパイラが必要で、1, 2, 4, 8 or 16バイトのメモリアクセスの前にasan_load系、asan_store系の関数を呼び出す必要がある。

これらの関数はアクセスするメモリの範囲がvalidかどうかをチェックする(アクセスするアドレスはシャドウメモリにマッピングされているので、実際にチェックするのはシャドウメモリのほうだろうけど)。もしinvalidなアドレスにアクセスしようとしたらエラーメッセージを表示する。

チェック関数のasan_load、asan_storeなどはcとかasmのコードから呼ぶんじゃなくてgccの機能を使ってそちらから呼び出させる感じになるんですね。gcc Instrumentationググるとそれらしいものが。

あとmemcpyとかの#defineによる置き換えなどもしていたり。

メールにあるTodoでもできるだけ速い処理にするってあって確かにそこは重要ですよね。こういった機能は便利なのは確かなので今後に期待ですね。

そういえば、このパッチの元ネタになる実装ではすでにいくつかバグを見つけて修正されたりもしているようですね(AddressSanitizerForKernel)。

NEW GAME! (1) (まんがタイムKRコミックス)

NEW GAME! (1) (まんがタイムKRコミックス)