FreeBSDの20周年と、mmap/ptrace脆弱性(CVE-2013-2171)

先日知ったけど、2013年6月18日でFreeBSDは誕生から20周年を迎えたらしい(FreeBSD、20 周年を迎える)。
思い返せば12,3年くらい前、NECのPC-9821 V10にFreeBSD(98) 2.2.8-RELEASEを入れたのが私の初めてのUNIXだった。いやはや、おめでとう。

と、そんなおめでたい雰囲気の中、FreeBSDカーネル脆弱性が見つかった(実は20周年の前日に見つかったから、前夜祭とでも言うべきだが)。mmap/ptraceを用いた権限昇格の脆弱性、つまり一般ユーザがroot権限を取得可能というもの。

既にExploitコードも出回っており、実際に試してみたところ確かにrootのシェルが実行できた。あはは。

ちなみに確認した環境は、FreeBSD 9.1(64bit)。

ワークアラウンドとして、sysctlでsecurity.bsd.unprivileged_proc_debugをゼロとすることが報告されている。試してみたところ、確かにこれでptraceが使えなくなるのでrootは取得できないことが確認できた。

# sysctl security.bsd.unprivileged_proc_debug=0

なお、これだけだと再起動すると無効になるため、reboot後も有効にしたければ/etc/sysctl.confに書く必要がある。

# echo 'security.bsd.unprivileged_proc_debug=0' >> /etc/sysctl.conf

ただしこの操作を行うと、デバッグ系のツールが使えなくなってしまう副作用がある。具体的にはgdbやtrussが使えなくなるため、これらが必要な環境ではパッチを当ててカーネルを再構築をするしかない。

で、せっかくなので今回の脆弱性が発見されたmmap/ptraceについてざっくり書いてみる。FreeBSDでもLinuxでも同じ話のはず。

mmapシステムコール

mmapはメモリマップドI/Oを提供するシステムコールで、ファイルシステム上にある実ファイルをメモリ(アドレス空間)にマッピングすることができる。これにより、read/writeシステムコールを利用せずにメモリへのアクセスだけでファイル操作をおこなえるようになる。

基本的な使い方としては、まず普通にファイルopenしてファイルディスクリプタを受け取り、そのfdに対してmmapする……という形で利用される。以下に少し詳しく使い方を見てみよう。

mmapの書式

man mmapすると分かるけど、mmap(2)の書式は以下のようになっている。

mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

第一引数のaddrは、マップするアドレス空間の先頭アドレスである。これはOSへの「ヒント」として使われ、ゼロならば勝手に選んでくれるので普通は明示的に指定する必要は無い。

第二引数のlenは、確保するアドレス空間の長さ。

第三引数protは、領域へのアクセス権の指定を表す。PROT_READ(読み込み)やPROT_WRITE(書き込み)の論理和(or)で指定する。

第四引数flagsは、他プロセスがマップされた領域を修正した際にどう共有するかを指定する。つまり、あるファイルへmmapした後に、他のプロセスがそのファイルを書き換えちゃったらどうする? ということ。MAP_PRIVATEならば修正はプロセス固有となる、つまり書き込み結果はファイルに反映されず各プロセス固有となる。一方、MAP_SHAREDならば書き込み結果はプロセス間で共有されファイルにも反映される。その他にも色々なフラグが用意されているけど、まぁ上記2つがほとんどかな。

第五引数fdは、マップするファイルディスクリプタ。普通はファイルをopenした際のfdを渡す。

第六引数はマップする際のオフセット値で、つまりaddr+offsetからアドレス空間を確保する。が、ここをゼロ以外で指定することって見たことないのでよく分からない。

よって、呼び出し例としては以下のような感じになる。

maddr = mmap((caddr_t)0, buf.st_size, PROT_READ, MAP_SHARED, fd, 0);

mmapシステムコールを実行すると、OSはファイルをメモリ上にマップする。この段階では、まだ実際にファイルの中身を読み込みはしない。その後に実際に読み込みが依頼されると、既にメモリに展開されていればそれを返すし、無ければ実際のファイルを読みに行ってメモリ上にコピーする。この動きは仮想メモリページフォールトと似ている。

つまり、ファイルシステム上のファイルへのアクセスをメモリのアドレス空間マッピングし、仮想メモリと同じようにページングの機構を用いてファイルアクセスを処理する……というのがmmapのイメージだ。

そんなことして何が嬉しいかと言うと、一般にはファイルアクセスの高速化。あるどデカいファイルがあって、複数のプロセスからそのファイルの一部へランダムアクセスがある際、mmapマッピングしてやればカーネル内のメモリ処理だけで操作が完結するため高速に動作する、はず。

ptrace

ptraceシステムコールは、実行中のプロセスのメモリおよびレジスタ状態へのアクセスを可能とする。動いているプロセスがメモリ上に確保している領域の内容を、ゴリゴリ書き換えちゃうなんてことができるので、使い方によってはかなり遊べる。

具体的な使い方は、以下のbkブログさんの記事がとても分かりやすかった。

うーん、何を書いてもこちらのblogに書かれている以上のことは書けそうにないので、これだけにしておく。このサンプルを試すと、表示しているhello, worldが動的に書き換わってとても面白いので、試してみることをオススメ。