つい先日,Linusが,次のような苦言を呈しました.
これは Memory Protection Extensions (MPX) という機能のサポートについてで,どうやらパッチ自体は悪くないけど,デフォルトで有効にされるパッチが含まれているのが気に要らないようでした.
ところで,MPXとはいったいなんでしょうか?
これは,具体的には配列の境界チェックをハードウェアでサポートする事で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
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つかう )
使い方は https://code.google.com/p/address-sanitizer/wiki/IntelMemoryProtectionExtensions に記述されてますが,情報が古いです.
ので以下に補足含めて説明.
環境がLinuxであることを前提で話を進めます.
よりライセンスの同意にチェックを入れた後に,
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をダウンロード
こちらも*のところはリビジョンやバージョン,日時が入りますが新しいものを選びましょう.
いまは配布されているバイナリで動作します.
サンプルコードはこの通り.そして,これを次のようにコンパイルします.
ここで,
なお,SDEでこれを試すのに,カーネルでセキュリティモジュールのYamaが有効な場合,ptrace_scopeを無効にする必要があります.
ちゃんとbnd*な命令が使われていることが確認できます.
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これで,準備は完了です.上記のサイトではGCCは自分でビルドするようにありますが,
$ 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
いまは配布されているバイナリで動作します.
コンパイル
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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でアセンブリコードに出力したものは以下になります.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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 |