Intel Memory Protection Extensions (MPX)

つい先日,Linusが,次のような苦言を呈しました.
Why would I want to enable this in my kernel when there are no actual CPU's out yet that support it? - https://patchwork.kernel.org/patch/5463081/
「この機能を実際にサポートした CPU がまだ出てないのにどうしてボクが自分のカーネルでこいつを有効にしたいと思うかい? 」
このような苦言にも関わらず,これは3.19へとマージされました.
これは Memory Protection Extensions (MPX) という機能のサポートについてで,どうやらパッチ自体は悪くないけど,デフォルトで有効にされるパッチが含まれているのが気に要らないようでした.
ところで,MPXとはいったいなんでしょうか?


MPXとは

MPXとは,Intelのtick-tackのtick,次の新アーキテクチャであるSkylakeにて実装予定の新機能です.
これは,具体的には配列の境界チェックをハードウェアでサポートする事でexploitを防ぐ機能となります.

Intel CPUの命令リファレンス[PDF]の第9章がまるまるその機能に割かれています.

MPXのため,BNDレジスタが4本追加され,このBNDレジスタは1本128bitです.
このレジスタは上位と下位でそれぞれアクセス可能で,64bitが2つという事になります.
BNDレジスタの上位はUB(UpperBound),下位はLB(LowerBound)と名付けられており,
勘が良い方は気がつかれたかもしれませんが,配列の上界のアドレスと下界のアドレスをそれぞれ格納します(そのため64bitが2つなんですね)

また,設定レジスタがユーザーモードのためにBNDCFGU,スーパーバイザーモードのためにIA32_BNDCFGS,状態レジスタとしてBNDSTATUSが追加されています.

レジスタ構成

拡張レジスタ



BNDCFGU,BNDCFGS

BNDSTATUS

命令

BNDMK b, m: BNDレジスタbにLowerBound(LB)とUpperBound(UB)を作成
BNDCL b, r/m: メモリ参照かレジスタにあるアドレスについて,LBをチェック
BNDCU b, r/m: メモリ参照かレジスタにあるアドレスについて,UBをチェック (1の補数表現)
BNDCNb, r/m: メモリ参照かレジスタにあるアドレスについて,UBをチェック (1の補数表現の否定)
BNDMOV b, b/m: BNDレジスタかメモリから,LBとUBをコピー/ロード
BNDMOV b/m, b: BNDレジスタかメモリに,BNDレジスタのLBとUBをストア
BNDLDX b, mib: 境界値のロード( SIB:Scale Index Baseアドレッシングを使う )
BNDSTX mib, b: 境界値のストア( BNDLDX同様SIBつかう )

実際に使う

Intelが提供するSDE( Software Development Emulator)を使う事で,実際に試す事ができます.SDEを用いると他にもAVX,AVX-512といったものも試せるので,対応プロセッサを持っていない人に良いのではないでしょうか.

使い方は https://code.google.com/p/address-sanitizer/wiki/IntelMemoryProtectionExtensions に記述されてますが,情報が古いです.
ので以下に補足含めて説明.
環境がLinuxであることを前提で話を進めます.

準備1:ダウンロード

まず,https://software.intel.com/en-us/protected-download/267266/144917
よりライセンスの同意にチェックを入れた後に,
sde-external-*-lin.tar.bz2と*-mpx-runtime-external-lin.tar.bz2をダウンロード.
*のところは日時,バージョンが入りますが,新しいものを選べば良いです.
次に,https://software.intel.com/en-us/articles/intel-software-development-emulator#gcc
よりgcc_install_5.0.0-mpx-*.tar.gzとbinutils-gdb_install_*.tar.gzをダウンロード
こちらも*のところはリビジョンやバージョン,日時が入りますが新しいものを選びましょう.

準備2:解凍,パス設定

では,準備を進めましょう.解凍時のディレクトリ名は自分の環境に合わせて変更してください.
$ mkdir $HOME/mpx_test
$ export MPX_HOME="$HOME/mpx_test"
$ cd $MPX_HOME
$ tar xvf ../sde-external-6.22.0-2014-03-06-lin.tar.bz2
$ tar xvf ../2014-02-13-mpx-runtime-external-lin.tar.bz2
$ tar xvf ../binutils-gdb_install_2.24.51.20140422.tar.gz
$ tar xvf ../gcc_install_5.0.0-mpx-r214719.tar.gz
$ export SDE_KIT=$MPX_HOME/sde-external-6.22.0-2014-03-06-lin
$ export MPX_RUNTIME_LIB=$MPX_HOME/2014-02-13-mpx-runtime-external-lin
$ export MPX_BINUTILS=$MPX_HOME/binutils-gdb_install_2.24.51.20140422
$ export MPX_GCC=$MPX_HOME/gcc_install_5.0.0-mpx-r214719
これで,準備は完了です.上記のサイトではGCCは自分でビルドするようにありますが,
いまは配布されているバイナリで動作します.

コンパイル

サンプルコードはこの通り.そして,これを次のようにコンパイルします.
$MPX_GCC/bin/gcc -fcheck-pointer-bounds -mmpx -L$MPX_RUNTIME_LIB -B$MPX_BINUTILS/bin -lmpx-runtime64 -Wl,-rpath,$MPX_RUNTIME_LIB global_buffer_overflow.c
参考サイトでは-fcheck-pointersというオプションでしたが,これは-fcheck-pointer-boundsに変更されているので注意を.
ここで,
$ CHKP_RT_MODE=count $SDE_KIT/sde -mpx-mode -- ./a.out
と実行すると……
g: 0x600bc0 0x600be8
Bound violation detected,status 0x1 at 0x400695
finishing
はい,ちゃんと境界をチェックできてますね.
なお,SDEでこれを試すのに,カーネルでセキュリティモジュールのYamaが有効な場合,ptrace_scopeを無効にする必要があります.
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
また,上記サンプルコードを-S -O0でアセンブリコードに出力したものは以下になります.
ちゃんとbnd*な命令が使われていることが確認できます.