Android 4.0 "Ice Cream Sandwich" の ASLR とその問題

Android 3.x までは、ASLR が全く存在しなかったことが問題のひとつでした。Android 4.0 "Ice Cream Sandwich" で ASLR が追加されたことにより、どこまで攻撃に強くなったのかを見てみようと考えました…が…エミュレータで試すとランダム要素がないという重大な問題に行き当たりました。

2 つの問題

ASLR がエミュレータ上でうまく働いていないのは、次の 2 つの問題が複合しているものと考えられます。

  • ARM における mmap のランダム性の問題
  • app_process コンパイラフラグの問題

ARM における mmap のランダム性の問題

この問題がある場合にはすべてのライブラリは 0x4000_0000 から順番に並び、ランダム性を失います。例えばこんな風に。

40000000-40000fff r-x  /library1.so
40001000-40006fff r-x  /library2.so
40007000-40009fff r-x  /library3.so

この根本原因は、ARM における mmap のランダム性の問題です。

ライブラリは mmap でロードされる

AOSP で sync したソースコードのうち、bionic/linker 中にある linker.c が、ライブラリをロードするためのコードを保持しています。ソースコードによれば、ライブラリは mmap でマップされた領域にロードされます。では mmap がメモリをランダマイズするのはどこで? 答えはカーネルの arch/.../mm/mmap.c です。ここは空きメモリ領域を、必要あらばランダム化して返すことが基本的な役目です。
しかしランダム化のコードが Android 2.6.35.7 ベースのとあるデバイスのソースコード中 (arch/arm/mm/mmap.c) には見当たりませんでした。結論から言えば、ARM の mmap にランダム性が追加されたのは比較的最近なのです。(x86 はずっと前にランダム化のためのコードがマージされています。)

カーネルのバージョン

Linux のコミットログを見てみると ARM の mmap ランダム化が導入されたのは 2.6.35-rc2 以降で、しかも本家の 2.6.35 にはマージされていません。メインラインへの統合は 2.6.36 で、当然これ以上のバージョンのカーネルを使わない限り、mmap で返されるアドレスはランダム化されません。
エミュレータは、といえば、どのバージョンも変わらず 2.6.29 ベース。そう、これが問題だったのです。たとえ prelink 情報を削除して ASLR が理論上効くようにしても、実際のロードに使用される mmap のランダム性がなければ意味がありません。Android エミュレータはちょうどこの問題に当たってしまったといえるでしょう。
一方の実機…例えば Galaxy Nexus は、すでに公開されたスクリーンショットに 3.0.1 との表記がありました。よって、ライブラリに関しては Android 4.0 の実機では ASLR が効くはずです。(私は実機を手に入れたわけではないので断定こそできませんが。)

app_process コンパイラフラグの問題

こちらは、「全くの別問題」です。前者の問題とは独立しています。この問題があるときには、app_process が全くランダム化されず、0x0000_8000 以降に配置されます。例えば次のように。

00008000-00009fff r-x  /system/bin/app_process
0000a000-0000afff rw-  /system/bin/app_process

こちらは mmap の問題ではなく、app_process をコンパイルするときの問題です。

PIE (Position Independent Executable)

PIE という種類のバイナリがあります。これは実行ごとにランダム化されることで、ASLR の恩恵を常に受けるタイプのバイナリです。コンパイラフラグは -fPIE、リンカフラグには -pie を付加することによって、この種のバイナリを作成することができます。
こうして生成されたバイナリには特徴があります。ELF ファイルということには変わりありませんが、ELF ファイルの種類を示すメンバーの値*1が通常の実行ファイルを示す ET_EXEC ではなく、共有ライブラリを示す ET_DYN となるのです。*2
じゃあ実際にコンパイルされた app_process を見てみると、…ET_EXEC ですorz
ET_EXEC じゃ何が悪いかというと、カーネル (fs/binfmt_elf.c) が ELF ヘッダに示された通りの固定のメモリアドレスを確保してしまいます。これにより ASLR が効かないようになっている? のです。

コンパイラフラグの問題

Android のツールチェーンは -fPIC を実行ファイルにつけますが、-fPIE はつけません。そして -fPIC つきで生成された実行ファイルは単なる PIC な実行ファイル (ET_EXEC) になってしまうのです…。
こちらに関しては、カーネルのバージョンでは解決しません。これは「仕様」だからです。

結論

ライブラリのランダム化に関しては、おそらく実機では問題ないでしょう。mmap のランダム化はお世辞にも強いとはいえません (一回の確保につき、256 通りのアドレスから 1 つ選ばれる) が、それでもエントロピーだけでいえば 32-bit 版 Windows (同じく 256 通りのアドレス中から 1 つ選ばれる) に相当します。ですが app_process は完全なミスに見えます。ASLR は実行コード全部が適切にランダム化されていることが前提であるため、このように一部でも実行可能なコードが固定されていると、exploit の可能性を残します。
幸いにも app_process は小さく、また低位アドレスに配置されているため、低いところに生っている実とはいえません。しかしながら、…ASLR がついたことをわざわざリリースノートに書いたのだったら、この問題くらいは解決しておいてほしかったです。