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は自分でビルドするようにありますが,
いまは配布されているバイナリで動作します.

コンパイル

#include <stdio.h>
int g[10];
int main(int argc, char **argv) {
printf("g: %p %p\n", g, g+10);
int x = g[argc * 10];
printf("finishing\n");
return x;
}
サンプルコードはこの通り.そして,これを次のようにコンパイルします.
$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でアセンブリコードに出力したものは以下になります.
.file "global_buffer_overflow.c"
.comm g,40,32
.section .rodata
.LC0:
.string "g: %p %p\n"
.LC1:
.string "finishing"
.text
.globl main
.type main, @function
main:
.LFB1:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $64, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
bndmov %bnd0, -48(%rbp)
bndmov __chkp_bounds_of_g(%rip), %bnd2
bndmov %bnd2, -64(%rbp)
movl $g+40, %edx
movl $g, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl -20(%rbp), %edx
movl %edx, %eax
sall $2, %eax
addl %edx, %eax
addl %eax, %eax
movl %eax, %edx
movslq %edx, %rax
salq $2, %rax
addq $g, %rax
bndmov -64(%rbp), %bnd2
bndcl (%rax), %bnd2
movslq %edx, %rax
salq $2, %rax
addq $g, %rax
addq $3, %rax
bndcu (%rax), %bnd2
movslq %edx, %rax
movl g(,%rax,4), %eax
movl %eax, -4(%rbp)
movl $.LC1, %edi
call puts
movl -4(%rbp), %eax
leave
.LCFI2:
bnd ret
.LFE1:
.size main, .-main
.globl __chkp_zero_bounds
.section .rodata.__chkp_zero_bounds,"aG",@progbits,__chkp_zero_bounds,comdat
.align 16
.type __chkp_zero_bounds, @object
.size __chkp_zero_bounds, 16
__chkp_zero_bounds:
.zero 16
.globl __chkp_none_bounds
.section .rodata.__chkp_none_bounds,"aG",@progbits,__chkp_none_bounds,comdat
.align 16
.type __chkp_none_bounds, @object
.size __chkp_none_bounds, 16
__chkp_none_bounds:
.quad -1
.quad -1
.data
.align 16
.type __chkp_bounds_of_g, @object
.size __chkp_bounds_of_g, 16
__chkp_bounds_of_g:
.quad g
.zero 8
.text
.type _GLOBAL__sub_B_00102_0_main, @function
_GLOBAL__sub_B_00102_0_main:
.LFB2:
pushq %rbp
.LCFI3:
movq %rsp, %rbp
.LCFI4:
movl $__chkp_bounds_of_g, %eax
movl $g, %edx
movq %rdx, (%rax)
movl $__chkp_bounds_of_g+8, %edx
movl $g, %eax
addq $39, %rax
notq %rax
movq %rax, (%rdx)
popq %rbp
.LCFI5:
ret
.LFE2:
.size _GLOBAL__sub_B_00102_0_main, .-_GLOBAL__sub_B_00102_0_main
.section .init_array.00102,"aw"
.align 8
.quad _GLOBAL__sub_B_00102_0_main
.section .eh_frame,"a",@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0
.byte 0x3
.string "zR"
.uleb128 0x1
.sleb128 -8
.uleb128 0x10
.uleb128 0x1
.byte 0x3
.byte 0xc
.uleb128 0x7
.uleb128 0x8
.byte 0x90
.uleb128 0x1
.align 8
.LECIE1:
.LSFDE1:
.long .LEFDE1-.LASFDE1
.LASFDE1:
.long .LASFDE1-.Lframe1
.long .LFB1
.long .LFE1-.LFB1
.uleb128 0
.byte 0x4
.long .LCFI0-.LFB1
.byte 0xe
.uleb128 0x10
.byte 0x86
.uleb128 0x2
.byte 0x4
.long .LCFI1-.LCFI0
.byte 0xd
.uleb128 0x6
.byte 0x4
.long .LCFI2-.LCFI1
.byte 0xc
.uleb128 0x7
.uleb128 0x8
.align 8
.LEFDE1:
.LSFDE3:
.long .LEFDE3-.LASFDE3
.LASFDE3:
.long .LASFDE3-.Lframe1
.long .LFB2
.long .LFE2-.LFB2
.uleb128 0
.byte 0x4
.long .LCFI3-.LFB2
.byte 0xe
.uleb128 0x10
.byte 0x86
.uleb128 0x2
.byte 0x4
.long .LCFI4-.LCFI3
.byte 0xd
.uleb128 0x6
.byte 0x4
.long .LCFI5-.LCFI4
.byte 0xc
.uleb128 0x7
.uleb128 0x8
.align 8
.LEFDE3:
.ident "GCC: (GNU) 5.0.0 20140925 (experimental)"
.section .note.GNU-stack,"",@progbits
ちゃんとbnd*な命令が使われていることが確認できます.