ファイルマップピング/無名メモリーリージョンの獲得(その2)
ファイルマップピング/無名メモリーリージョンの獲得での、exe-shieldにかかるarch_get_unmapped_exec_area関数がどのような物かについてです。
arch_get_unmapped_exec_area関数は、シェアーライブラリを0x00110000-0x01000000のアドレス内でランダムに割り付けようとするもののようです。なお、arch_get_unmapped_area/arch_get_unmapped_area_topdown関数では、それぞれの割り当て範囲内で、処理効率のためmm->cached_hole_size/mm->free_area_cacheを考慮して、arch_get_unmapped_area関数は高位アドレスへ、arch_get_unmapped_area_topdown関数は低位アドレスへと、そのアドレスを取得しています。
ランダムに割り付けることにより、バッファーオーバフローに対するセイキュリティを高めることができます。バッファーオーバフローは、悪意なコードをシステムの脆弱性をついて、任意のバッファー上に書き込んで、そのアドレスをスタックの返り値に書き込むことで、その関数のret時、埋め込んだ悪意のコードを実行させるというものです。従って攻撃されやすいシェアーライブラリを0x00110000-0x01000000にランダムに配置することで、その対策になるということです。なお0x00110000-0x01000000あたりの領域を、ASCII-Armor領域というようです。
獲得できなければ、addr = SHLIB_BASEとしてフル検索していきます。条件として、取得領域がヒープ領域を超えてはいけないようです。なおaddrが0x01000000より小さいとき(ASCII-Armor 領域)はそのままaddrを返しています。addr = vma->vm_endで検索しているように、ASCII-Armor 領域内の低アドレスのvmaから順に割り当てていくようですが・・・? そういう意味ではボトムアップで、レガシーな割り当て。ってことですが。
addr が0x01000000を超えた場合、0x01000000からヒープの最初か、0x08000000の間の乱数で、取得できるものなら取得しているようです。できるだけランダムに取得するということのようです。
そして、これらにおいて取得できなければ、get_unmapped_area関数により、通常(?)に取得するようです。要は、セキュリティ向上のため、できるだけASCII-Armor 領域から、しかもできるだけランダムに割り当てようとする処理のようです。
しかし、攻撃されるライブラリがASCII-Armor領域だちどうなるでしょうか?悪意のプログラムのアドレスは必ず0x00ちょめちょめ、となってしまい、そうなるとstrcpyに渡す文字列には、かならず0x00,すなわちNULL文字を含んだ文字列を渡す必要があります。そうなるとそれ以降の文字列(悪意の関数アドレスの残り)を引き渡す事ができなくなってしまいます。結果、セグメントエラーとなるだけで、悪意のコードの実行を防ぐことができるわけです。
arch_get_unmapped_exec_area関数は、シェアーライブラリを0x00110000-0x01000000のアドレス内でランダムに割り付けようとするもののようです。なお、arch_get_unmapped_area/arch_get_unmapped_area_topdown関数では、それぞれの割り当て範囲内で、処理効率のためmm->cached_hole_size/mm->free_area_cacheを考慮して、arch_get_unmapped_area関数は高位アドレスへ、arch_get_unmapped_area_topdown関数は低位アドレスへと、そのアドレスを取得しています。
ランダムに割り付けることにより、バッファーオーバフローに対するセイキュリティを高めることができます。バッファーオーバフローは、悪意なコードをシステムの脆弱性をついて、任意のバッファー上に書き込んで、そのアドレスをスタックの返り値に書き込むことで、その関数のret時、埋め込んだ悪意のコードを実行させるというものです。従って攻撃されやすいシェアーライブラリを0x00110000-0x01000000にランダムに配置することで、その対策になるということです。なお0x00110000-0x01000000あたりの領域を、ASCII-Armor領域というようです。
#define SHLIB_BASE 0x00110000 unsigned long arch_get_unmapped_exec_area(struct file *filp, unsigned long addr0, unsigned long len0, unsigned long pgoff, unsigned long flags) { : : if (!addr) addr = randomize_range(SHLIB_BASE, 0x01000000, len); if (addr) { addr = PAGE_ALIGN(addr); vma = find_vma(mm, addr); if (TASK_SIZE - len >= addr && (!vma || addr + len <= vma->vm_start)) return addr; } addr = SHLIB_BASE; for (vma = find_vma(mm, addr); ; vma = vma->vm_next) { if (TASK_SIZE - len < addr) return -ENOMEM; if (!vma || addr + len <= vma->vm_start) { if (addr + len > mm->brk) goto failed; if (addr >= 0x01000000) { tmp = randomize_range(0x01000000, PAGE_ALIGN(max(mm->start_brk, (unsigned long)0x08000000)), len); vma = find_vma(mm, tmp); if (TASK_SIZE - len >= tmp && (!vma || tmp + len <= vma->vm_start)) return tmp; } return addr; } addr = vma->vm_end; } failed: return current->mm->get_unmapped_area(filp, addr0, len0, pgoff, flags); }randomize_range関数で、0x00110000(SHLIB_BASE)から0x01000000範囲で、ランダムのアドレスを取得し、そのアドレスでメモリージョン獲得できるかチェックします。find_vma関数は指定されたアドレスがvma->vm_endより小さい最初のvmaを返します。もし該当するvmaがなければNULLを返します。従って!vmaだと、addr以降メモリーリージョンがない。addr + len <= vma->vm_startで、addreからlenサイズのメモリーリージョンと重なりは無いということで、メモリーリージョンが獲得できるアドレスということです。
獲得できなければ、addr = SHLIB_BASEとしてフル検索していきます。条件として、取得領域がヒープ領域を超えてはいけないようです。なおaddrが0x01000000より小さいとき(ASCII-Armor 領域)はそのままaddrを返しています。addr = vma->vm_endで検索しているように、ASCII-Armor 領域内の低アドレスのvmaから順に割り当てていくようですが・・・? そういう意味ではボトムアップで、レガシーな割り当て。ってことですが。
addr が0x01000000を超えた場合、0x01000000からヒープの最初か、0x08000000の間の乱数で、取得できるものなら取得しているようです。できるだけランダムに取得するということのようです。
そして、これらにおいて取得できなければ、get_unmapped_area関数により、通常(?)に取得するようです。要は、セキュリティ向上のため、できるだけASCII-Armor 領域から、しかもできるだけランダムに割り当てようとする処理のようです。
補足
ASCII-Armor 領域とは、0x01000000までの16Mの領域を言うそうです。バッファーオーバフロー攻撃は、バッファーオーバフローで埋め込んだ悪意のあるプログラムのアドレスを、スタックの関数の返り値に上書きする必要があります。通常の処理ではユーザから渡された引数は、NULL文字としてstrcpy関数でローカル変数上のバッファーに複写しますが、ローカル変数の次には関数の返り値があります。もし文字サイズチェックをしていない処理では、バッファーサイズを超えたところに、悪意のあるプログラムのアドレスを付加することで、スタックの返り値にこの悪意のプログラムのアドレスを上書きするというものです。しかし、攻撃されるライブラリがASCII-Armor領域だちどうなるでしょうか?悪意のプログラムのアドレスは必ず0x00ちょめちょめ、となってしまい、そうなるとstrcpyに渡す文字列には、かならず0x00,すなわちNULL文字を含んだ文字列を渡す必要があります。そうなるとそれ以降の文字列(悪意の関数アドレスの残り)を引き渡す事ができなくなってしまいます。結果、セグメントエラーとなるだけで、悪意のコードの実行を防ぐことができるわけです。