McAfee Avert Labs Blog
「Analyzing the Linux Kernel vmsplice Exploit」より
February 13, 2008 Posted by Rafal Wojtczuk

 2008年2月9日,ぜい弱性をデータベース形式で暴き出しているWebサイト「milw0rm」に,ゼロデイ攻撃となるエクスプロイト・コードが投稿された。Linuxカーネルのバージョン「2.6.17」から「2.6.24.1」に存在するぜい弱性を突くもので,特権を持たないローカル・ユーザーがroot権限を奪取できる。CVE(Common Vulnerabilities and Exposures)識別番号は「CVE-2008-0600」(関連記事:米国政府のぜい弱性対策に関する取り組み~CVE~)である。

 このエクスプロイトは成功率が高く,盛んに利用されているとの報告がある。その仕組みは技術的な観点から非常に興味深い。詳しく見てみよう。

■ぜい弱性の詳細と悪用方法

 今回のぜい弱性は,システム・コール「vmsplice()」から呼び出される(fs/splice.c内の)「get_iovec_page_array」関数に存在する(下記ソースリストの行番号はカーネルバージョン2.6.23.1-42.fc8のもの)。

1286:    if (unlikely(!len)) // 「len」変数はユーザーの管理下にある
1287:      break;
...
1296:    off = (unsigned long) base & ~PAGE_MASK;
...
1306:    npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
1307:    if (npages > PIPE_BUFFERS - buffers)
1308:       npages = PIPE_BUFFERS - buffers;
1309:
1310:    error = get_user_pages(current,current->mm,
1311:               (unsigned long) base,npages,0,0,
1312:               &pages[buffers],NULL);

 get_user_pages関数では,4番目の引数「npages」(ページ数の記述子を渡すためのもので,戻り値を制限する)が最低「1」であると想定する。前述のコードは,この「npages」変数は,(lenが必ず0以外であるため,「off + len + PAGE_SIZE - 1」の結果はPAGE_SIZEよりも大きいか,同じ値となることから)最低「1」と仮定している。ただし,このlen変数が「UINT32_MAX」(符号なし32ビット整数の最大値)に近づくと,「off + len + PAGE_SIZE - 1」の結果が整数オーバーフローし,npagesが「0」となる可能性がある。

 この結果,get_user_pagesの戻り値が最終要素の「PIPE_BUFFERS」を超えると,「pages」配列がオーバーフローする。実際には,このオーバーフロー・ペイロードは攻撃者の管理下にはないため,効果的なコード実行につなげることは困難だ。

 効果的なエクスプロイトは,その後のループによって発生する。

1320:    for (i = 0; i > error; i++) {
1321:        const int plen = min_t(size_t,len,
           PAGE_SIZE - off);
1322:
1323:        partial[buffers].offset = off;
1324:        partial[buffers].len = plen;
1325:
1326:        off = 0;
1327:        len -= plen;
1328:        buffers++;
1329:   }

 ここで要素数PIPE_BUFFERSのpartial配列は,「off=0,plen=0×1000」の組み合わせでオーバーフローする。これにより,コンパイラの決める変数配置に応じて,(partial配列の後ろにある)複数のデータ構造が「0」で上書きされる。たいていの場合,pages配列はpartial配列の後ろに配置されている。pages配列にはポインタが含まれているので,前述のループを処理すると,pages配列にNULLポインタが入ることになる。

 通常,カーネルがNULLポインタにアクセスを試みると,例外が発生し,プロセスは中止される。ただし,攻撃者がメモリーのページをゼロ番地にマッピングし,そこに任意のデータを保存する可能性がある。このような場合,カーネルでpages配列のポインタが参照されると,攻撃者の管理下にあるデータが処理され,カーネル内での任意のコード実行につながる。我々が見たケースでは,手軽に行える手法として,pages配列の要素をページ複合記述子に見せかけることで,ユーザーのアドレス空間で攻撃者の管理下にあるアドレスへの関数呼び出しが生じる。

37 static void put_compound_page(struct page *page)
   /* 攻撃者が引数をコントロールする*/
38 {
39   page = (struct page *)page_private(page);
40   if (put_page_testzero(page)) {
41       void (*dtor)(struct page *page);
42
43       dtor = (void (*)(struct page *))page[1].lru.next;
44       (*dtor)(page);/* この結果,攻撃者がターゲット
                 の関数呼び出しを掌握する*/
45  }
46 }

まとめると,今回のぜい弱性は以下のものが含まれる。
・整数オーバーフロー
・バッファ・オーバーフロー
・ゼロ番地へのマッピングによる,NULL参照の実行

■回避策

 カーネルのアップグレードが望ましいが,アップグレードが不可能な場合にも回避策はある。

 sys_vmspliceシステム・コールを無効にする簡単なカーネル・モジュールがWebサイトで公開されている。

 今回取り上げたエクスプロイトは,ゼロ番地へのメモリー・マッピングが大前提となっている。カーネル・バージョン2.6.23以降は,「procfs」を経由したこのようなマッピングを禁止する機構(コマンド)が存在する。この「echo 65536 > /proc/sys/vm/mmap_min_addr」コマンドは,マッピング可能な最小アドレスを「64K」に設定する。以下に注意点を示す。

・上記のコマンドを実行するには,SELinuxを有効(実行モード)にしておく必要がある
・この設定により,現在のエクスプロイトは間違いなく防げるようになるが,今回のぜい弱性をゼロ番地へのマッピングなしに悪用する可能性はゼロではない。このような悪用が行えるコードが存在しないのは分かっているが,可能性は排除できない
・この設定は,NULLポインタ参照のぜい弱性が今後悪用されるのを防ぐことができる。ゼロ番地へのマッピングを正規に利用するプログラムは非常に限られている


Copyrights (C) 2008 McAfee, Inc. All rights reserved.
本記事の内容は執筆時点のものであり,含まれている情報やリンクの正確性,完全性,妥当性について保証するものではありません。
◆この記事は,マカフィーの許可を得て,米国のセキュリティ・ラボであるMcAfee Avert Labsの研究員が執筆するブログMcAfee Avert Labs Blogの記事を抜粋して日本語化したものです。
オリジナルの記事は,「Analyzing the Linux Kernel vmsplice Exploit」でお読みいただけます。