10年以上前からあるLinux Kernelの懸念点が脆弱性として発見された「Stack Clash」――意図的に公開が遅らされた理由OSS脆弱性ウォッチ(2)(1/2 ページ)

連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェアの脆弱性に関する情報を取り上げ、解説していく。今回は、2017年9月14〜15日に開催された「Linux Security Summit」でも話題になっていた「Stack Clash」の詳しい説明と情報をまとめる。

» 2017年09月28日 05時00分 公開
[面和毅OSSセキュリティ技術の会]

 「OSSセキュリティ技術の会」の面和毅です。本連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェア(OSS)の脆弱(ぜいじゃく)性に関する情報を取り上げ、解説していきます。

 2017年6月20日に、「Stack Clash」と呼ばれる一連の脆弱性が報告されました。少し時間がたってしまっていますが、2017年9月14〜15日に開催された「Linux Security Summit」でも話題になっていたこともあり、今回はStack Clashの詳しい説明と情報をまとめます。

脆弱性発見の経緯

 このStack Clashは、glibcやカーネルなどに関わる問題で、かつ“スタックガードページ”の実装方法に直接関係することが多いため、2017年6月20日まで公開されず、各Linuxディストリビューションや主要OSSのみに報告されて修正が行われていたものです。

 今回のStack Clashは、以下のような一連の脆弱性から構成されています。

  • Linux Kernelのスタックガードページの脆弱性(CVE-2017-1000364)
  • Linux KernelでRLIMIT_STACK/RLIM_INFINITYの制限を、引数や環境変数が迂回できてしまう脆弱性(CVE-2017-1000365)
  • glibcでLD_LIBRARY_PATHの値に細工をすることでヒープ/スタックの値を操作できる脆弱性(CVE-2017-1000366)
  • offset2libの問題で、PIEバイナリに引数や環境変数として1GBのデータを使うことが可能な脆弱性(CVE-2017-1000370)
  • offset2libの問題で、PIEバイナリの引数や環境変数を細工することができる、またスタックガードページを飛び越しやすくすることができる脆弱性(CVE-2017-1000371)
  • Linux Kernelでld.soやヒープと、スタックアドレス間の距離が最小で128MBになっているという脆弱性(CVE-2017-1000379)

今回発見された脆弱性の概要

 この脆弱性は、「user-spaceのプロセスのスタックやヒープが衝突(Clash)した場合にどうなるか」という長期間(実際、12年間にわたっています)の疑問の延長にある問題です(そういう意味では、決して新しいものではなく、古くからある脆弱性といえます)。

 この「スタックやヒープの衝突」という問題を理解するために、まずはプロセスを起動した際のメモリ領域の情報を見てみましょう。例として挙げるのは、sleep 3000を実行した際の/proc/[pid]/mapsです(図1)。

図1
  • [heap](ヒープ)
  • ld.soのread-writeセグメント
  • 空白(Anonymous mmap)
  • [stack](スタック)

 このように出力されたスタックとヒープを増大させたり、ld.soやAnonymousmmapを増大させたりすることで、これらの領域を衝突させて好きなように書き換えることはできないかというものです。

 このようなスタックやヒープの衝突の例は、既に幾つかありました。

  • 2005年に、Apache 2.0.53上のmod_php 4.3.0における、user-spaceでのStack Clashの脆弱性とexploitが報告されている(参考PDF
  • 2010年には、Xorgサーバのuser-spaceでのStack Clashの脆弱性とexploitが報告されている(参考PDF
  • 2010年以降では、kernel-spaceでの幾つかのexploitが報告されている(参考PDF、参考リンク12

 しかし、user-spaceでは、先の2005年のものと2010年のものだけになっています。それは、Linux上でStack Clashを防ぐための保護機能(スタックの下に“スタックガードページ”を設けるもの)が設けられたためです。スタックガードページはCVE-2010-2240の問題を回避する際に導入されました。

 2017年6月20日にQualysから公開された脆弱性のアドバイザリによると、幾つかの“スタックガードページ”の実装方法に問題があり、これをuser-spaceで破る方法があるということです。

スタックガードページとは

 ユーザープロセスは起動時にスタックなどをメモリ領域にマッピングします。このスタックの位置はスタックポインタ(i386のespレジスタ)で表されますが、これはスタックに積むものが大きくなるにつれて下側に下がってきます。そして、スタックの下限(スタートアドレス)に到達した際には、ページフォルト例外が発生し、スタック開始アドレスが下側に拡張されます。これはスタックのサイズがRLIMIT_STACK(ulimit -sコマンドで表示。通常のデフォルトは8192KB)になるまで続けられます。そして、RLIMIT_STACKまで達してこれ以上拡張できなくなると、SIGSEGVなどが発生してプロセスが終了します(図2)。

図2

 しかし、この拡張だと、スタックのすぐ下に他のメモリ領域などが来ていた際にオーバーラップして書き換えられてしまいます(CVE-2010-2240など)。これを防ぐため、スタックガードページが設けられました。これは数KB(1Page:i686の場合4096KB)をスタック開始アドレスの下側にあらかじめ付けておき、このガードページにアクセスがあった場合には「ページフォルト例外」を発生させ、プロセスに対してSIGSEGVを発生させて終了させます(図3、図4)。

図3
図4

 これにより、スタックが拡張していくときに、他のメモリ領域(他のプロセスのヒープ領域など)がスタックと重ならないようになります。

 一般的に、メモリを増加させるのは“連続的な変化”であるため、このスタックガードページは有効です。しかし、今回の「Linux Kernelのスタックガードページの脆弱性(CVE-2017-1000364)」においては、数KB程度のスタックガードページでは、スタックガードページにアクセスすることなく、“飛び越してしまう”抜け道が発覚しています。これを緩和するには、スタックガードページのサイズを増加させることが有効になります。

攻撃の概略

 このStack Clashは、大まかに以下の手順で実施できます。

  1. 衝突(スタックを他のメモリ領域と衝突させる)
  2. 実行(スタックポインタをスタックの開始アドレスに移動する)
  3. ジャンプ(スタックガードページを飛び越えて他のメモリ領域まで到達させる)
  4. 破壊(スタックや他のメモリ領域の破壊)

【ステップ1】衝突(スタックを他のメモリ領域と衝突させる)

 スタックの開始アドレスが他のメモリ領域の上限に当たるまで、または他のメモリ領域の上限がスタック開始アドレスに達するまでメモリを割り当てます。他のメモリ領域の例は下記です。

  • ヒープ
  • Anonymous mmap()
  • ld.soのread-writeセグメント
  • PIE(Position-Independent Executable:位置独立実行形式)のread-writeセグメント

 このステップではメモリを割り当てる際に下記を利用します。

  • スタックとヒープ
  • スタックとAnonymous mmap()メモリ
  • スタックのみ

 なお「ヒープとAnonymous mmap()」は、LinuxではLD_AUDITのメモリリーク(glibc: CVE-2017-1000366)を利用します。

 スタックを割り当てに使う場合は、コマンドラインの引数と環境変数を利用します。LinuxではRLIMIT_STACKの4分の1です(man execve参照)。しかし、「Linux KernelでRLIMIT_STACK/RLIM_INFINITYの制限を、引数や環境変数が迂回できてしまう脆弱性(CVE-2017-1000365)」を利用することで、アプリケーションに依存せずにメモリの割り当てが可能になっています。

【ステップ2】実行(スタックポインタをスタックの開始アドレスに移動する)

 スタックポインタは通常スタック開始アドレスの数KB上にあります。Linuxでは、ポインタのargv[]とenvp[]配列を利用して、最初のスタック拡張の128KBを消費できます。

【ステップ3】ジャンプ(スタックガードページを飛び越えて他のメモリ領域まで到達させる)

 スタックポインタをスタックガードページに触れさせずに、スタックから【ステップ1】で衝突させた範囲まで移動させます。この【ステップ3】では、下記のような条件を満たす、大きなスタックベースのバッファー、alloca()、VLA(可変長配列)のいずれかが必要になります。

  • スタックガードページよりも大きい
  • 終了位置はスタックガードページより上のスタック内
  • 開始位置はスタックガードページよりも下の領域
  • 全て書かれている必要はない

 「Qualys Security Advisory」では、Linux上で、スタックガードページを飛び越えさせる一般的な方法として、a「LANGUAGE環境変数を用いる」、b「ld.soのLD_LIBRARY_PATH環境変数のコピーであるllp_tmpを用いる」など幾つか考えられています。

【ステップ4】破壊(スタックや他のメモリ領域の破壊)

 【ステップ4】は、前述の【ステップ3】におけるa、bから2通りに分けられます。

 4a:スタック(espがまだポイントしている状態)以下のメモリ領域に書き込みます。この方法は、exim、sudo、suを用いた攻略時に使用されます。

 4b:スタックに書き込んで衝突しているメモリ領域を破壊します。これはld.soを用いた攻略時に使用されます。この場合は、アプリケーション固有のものになります。

       1|2 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。