スマートフォン解析 top

TOP > タイガーチームセキュリティレポート > Linux Kernel fork(2)のSYSRET脆弱性(CVE-2014-4699)

タイガーチームセキュリティレポート

Linux Kernel fork(2)のSYSRET脆弱性(CVE-2014-4699)

今回はhardwareがらみの脆弱性がテーマです。常日頃からKernelに関する脆弱性をwatchしてきた方であれば、この脆弱性に既視感を覚えるかもしれませんが、これは過去に報告されたLinux Kernelの脆弱性CVE-2006-0744や、WindowsやFreeBSDなどの複数のOSにおける権限昇格の脆弱性JVNVU#649219と同系列のIntelの64bit CPUのSYSRET命令の仕様に起因する脆弱性です。


-影響-

3.15.4未満のLinux KernelにはIntelの64bit CPU上でSYSRET命令を実行する際のレジスタの値を操作されることにより権限昇格またはDoS攻撃につながる脆弱性があります。ベンダからの情報をもとに対策をお勧めします。


-原理-

この脆弱性の原理はCVE-2006-0744やJVNVU#649219と同じです。

AMDとIntelのSYSRET命令には例外の取り扱いに関する微妙な仕様の違いがあります。プログラムがSYSRET命令でsystem callから復帰する際にはRCXレジスタに格納された戻り先のアドレスをRIPレジスタにコピーする動作が発生しますが、このときRCXの値がnon-canonical addressである場合に発生する#GPがAMDではring3で実行されるのに対しIntelではring0で実行されます。

この仕様の違いは、SYSRET命令を実行する際にring3の攻撃者がいくつかのレジスタの値をコントロール可能な状況下で、セキュリティ上のインパクトをもたらします。SYSRET命令の例外がring0で実行される場合にはRSPの値がリストアされずに保たれるのでRSPがKernel空間の書き込み可能なメモリ領域を指す場合そのアドレスに配置されたデータは例外レコードやpushされたレジスタの値により上書きされます。

これを利用すると、攻撃者は例えばIDT(Interrupt Descriptor Table)にある関数ポインタを書き換えて例外ハンドラの制御を奪うことでshellcodeを実行し権限昇格やDoS等の攻撃を成功させることが可能になります。IntelのSYSRETに関する動作はまさにSDMに書いてある仕様通りなのでIntelのCPUのバグではありませんが、OSの開発者はAMDとIntelの挙動の違いに注意する必要があります。


-fork()-

問題のfork()ですが、今回の脆弱性はptrace()でattachされたプロセス("tracee")がfork()した場合に、親("tracer")にイベントを通知するpathがptrace_event()を経由してSYSRETで復帰するという流れであったためtraceeがSYSRETする前にtracerがtraceeのレジスタの値を適当な値に書き換えることができ、上で説明したようにring0で#GPが発生して任意のコード実行にいたるというものだったようです。

この脆弱性への対策として、traceeがfork()した場合にSYSRETではなくIRETで復帰するようにcommit b9cd18de4db3c9ffa7e17b0dc0ca99ed5aa4d43aで修正されました。

diff --git a/arch/x86/include/asm/ptrace.h b/arch/x86/include/asm/ptrace.h
index 14fd6fd..6205f0c 100644
--- a/arch/x86/include/asm/ptrace.h
+++ b/arch/x86/include/asm/ptrace.h
@@ -231,6 +231,22 @@ static inline unsigned long regs_get_kernel_stack_nth(struct pt_regs *regs,

 #define ARCH_HAS_USER_SINGLE_STEP_INFO

+/*
+ * When hitting ptrace_stop(), we cannot return using SYSRET because
+ * that does not restore the full CPU state, only a minimal set. The
+ * ptracer can change arbitrary register values, which is usually okay
+ * because the usual ptrace stops run off the signal delivery path which
+ * forces IRET; however, ptrace_event() stops happen in arbitrary places
+ * in the kernel and don't force IRET path.
+ *
+ * So force IRET path after a ptrace stop.
+ */
+#define arch_ptrace_stop_needed(code, info) \
+({ \
+ set_thread_flag(TIF_NOTIFY_RESUME); \
+ false; \
+})
+
 struct user_desc;
 extern int do_get_thread_area(struct task_struct *p, int idx,
                              struct user_desc __user *info);
kernel/git/torvalds/linux.git


-PoC 検証-

Vitaly NikolenkoがPoCを公開しています。このexploitは先ほど説明したIDTを書き換えるtraditionalな手法をとっていますが、最近のLinux KernelではIDTがread-onlyになっているため新しいKernelに対してこの方法は適用できません。ただし、それ以外の重要なデータを上書きしてDoSを発生させるのは容易です。

Ubuntu 12.04で検証を行いました。Kernelは3.2.0-23-genericです。exploitを実行するとfork()後にレジスタの値を書き換えられたtraceeでdouble faultが発生した後でtraceeは"クリーン"に異常終了しますが、ログをみると以下のようにレジスタの値(ここではRSP)が任意の値(0xdeadbeef)に書き換えられていることがわかります。権限昇格には至っていませんがこの時点でexploitはほとんど成功しています

Jul 31 12:28:23 precise kernel: [  124.812007] RIP: 0010:[<ffffffff81664ae3>]  [<ffffffff81664ae3>] sysret_check+0x57/0x5a
Jul 31 12:28:23 precise kernel: [  124.812007] RSP: 0018:00000000deadbeef  EFLAGS: 00010046
Jul 31 12:28:23 precise kernel: [  124.812007] RAX: 0000000000000832 RBX: 81668e0000106ac0 RCX: 8fffffffffffffff
Jul 31 12:28:23 precise kernel: [  124.812007] RDX: 00000000ffffffff RSI: 81658e000010cbd0 RDI: 0000000000000000
Jul 31 12:28:23 precise kernel: [  124.812007] RBP: 00000000ffffffff R08: 81658e010010cb00 R09: 00000000ffffffff
Jul 31 12:28:23 precise kernel: [  124.812007] R10: 81668e0000106b10 R11: 0000000000000246 R12: 81668e0000106ac0
Jul 31 12:28:23 precise kernel: [  124.812007] R13: 00000000ffffffff R14: 81668e0200106a90 R15: 00000000ffffffff
Jul 31 12:28:23 precise kernel: [  124.812007] FS:  00007f4fa0f5b700(0000) GS:ffff88003fc00000(0000) knlGS:0000000000000000
Jul 31 12:28:23 precise kernel: [  124.812007] CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b
Jul 31 12:28:23 precise kernel: [  124.812007] CR2: 00000000deadbed8 CR3: 000000003bbee000 CR4: 00000000000006f0
Jul 31 12:28:23 precise kernel: [  124.812007] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
Jul 31 12:28:23 precise kernel: [  124.812007] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
Jul 31 12:28:23 precise kernel: [  124.812007] Process a.out (pid: 2097, threadinfo ffff88003bef0000, task ffff8800237e16f0)
Jul 31 12:28:23 precise kernel: [  124.812007] Stack:
Jul 31 12:28:23 precise kernel: [  124.812007] BUG: unable to handle kernel paging request at 00000000deadbeef
Jul 31 12:28:23 precise kernel: [  124.812007] IP: [<ffffffff81015730>] show_stack_log_lvl+0x120/0x190
Jul 31 12:28:23 precise kernel: [  124.812007] PGD 3a401067 PUD 0 
Jul 31 12:28:23 precise kernel: [  124.812007] Oops: 0000 [#2] SMP 
Jul 31 12:28:23 precise kernel: [  124.812007] CPU 0 

以上、CVE-2014-4699の検証でした。この脆弱性はhardwareに完全に依存している点で、興味深い脆弱性だと思います。Intelはこの仕様を変更することはないでしょうから、またこの仕様に起因する脆弱性に(もしかしたら今度は別のOSで!)出会えるかもしれません


参考情報:
[1] ptrace,x86: force IRET path after a ptrace_stop()
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=b9cd18de4db3c9ffa7e17b0dc0ca99ed5aa4d43a
[2] oss-security - Re: CVE-2014-4699: Linux ptrace bug
http://www.openwall.com/lists/oss-security/2014/07/08/16
[3] JVNVU#649219
http://jvn.jp/vu/JVNVU649219/
[4] CVE-2006-0744
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-0744
[5] Intel 64 and IA-32 Architectures Software Developer Manuals
http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
[6] CVE-2014-4699: Linux Kernel ptrace/sysret vulnerability analysis
http://hashcrack.org/page?n=21072014


タイガーチームメンバー 塚本 泰三