当内存安全变得不不再安全 Mingshen Sun, Yulong Zhang, Tao Wei Baidu X-Lab DEF CON China May, 2018
whoami 百度安全实验室, 高级安全研究员 PhD, 香港中 文 大学 系统安全 移动安全 IoT 安全 车辆安全 MesaLock Linux( 内存安全的 Linux 发 行行版 ) TaintART 项 目维护者, etc. mssun @ GitHub https://mssun.me!2
内容概述 内存破坏以及内存安全 内存安全的编程语 言 当内存安全变得不不再安全 非内存安全的代码编写指南 总结!3
内存破坏与内存安全 当 一段程序的内存在运 行行时被修改, 并且是 非故意的修改, 我们把它称之为内存破坏, 也称为破坏内存安全的 行行为 代码破坏攻击 控制流劫持攻击 数据攻击 内存信息泄漏漏!4
SoK: Eternal War in Memory Laszlo Szekeres, Mathias Payer, Tao Wei, Dawn Song Proceedings of the 2013 IEEE Symposium on Security and Privacy 1 Make a pointer go out of bounds Make a pointer become dangling 内存安全 2 Use pointer to write (or free) Use pointer to read VI. Memory Safety 3 Modify a data pointer Modify code... Code Integrity Modify a code pointer... VIII.A. Code Pointer Integrity Modify a data variable... VII.A. Data Integrity Output data variable 4 to the attacker specified code Instruction Set Randomization to the address of shellcode / gadget V.A. Address Space Randomization to the attacker specified value Interpret the output data V.B. Data Space Randomization 5 Use pointer by indirect call/jump Use pointer by return instruction Use corrupted data variable VIII.B. Control-flow Integrity VII.B. Data-flow Integrity 6 Execute available gadgets / functions Execute injected shellcode Non-executable Data / Instruction Set Randomization Code corruption attack Control-flow hijack attack!5 Data-only attack Information leak
缓解内存破坏的 一些 方法 程序分析例例如符号执 行行 :KLEE 内存检查虚拟机 :Valgrind 编译器器检查 :AddressSanitizer Fuzzing: AFL, libfuzzer 编程语 言 : Rust, Go!6
内存安全的编程语 言 基于垃圾回收器器的内存管理理 Go 语 言结合了了解释的动态类型语 言的 方便便性 与静态类型编译的 高效和安全性 所有权 (ownership) / 借 用 (borrowing) 的内存模型 Rust 语 言是 一个系统编程语 言, 运 行行速度快, 避免 segfaults 并且保证线程安全!7
所有权 (ownership) / 借 用 (borrowing) 的内存模型 编译器器强制规则 : 每个资源有 一个拥有者 Aliasing + Mutation 其他 人只能有限制的借 用这个资源 (e.g., 创建 一个 alias) 拥有者不不能释放已经被借 用的资源!8
Use After Free in C/Rust void func() { int *mem = malloc(sizeof(int)); C/C++ free(mem); } printf("%d", *mem); fn main() { let mem = String::from("Hello World"); let mut mem_ref = &mem; { let new_mem = String::from("Goodbye"); mem_ref = &new_mem; } println!("name is {}", &mem_ref); } Rust!9
Compile a UAF toy example in Rust!10
使 用 Rust 重写 浏览器器 : Servo, Firefox 操作系统内核 : Redox OS kernel, Tock OS kernel 数字货币 : parity 系统 工具 : coreutils, ion shell!11
MesaLock Linux: 一个内存安全的 Linux 发 行行版 用 Rust Go 等内存安全语 言重写 用户空间应 用 (user space applications), 以在 用户空间中逐步消除 高危的内存 安全漏漏洞洞 在 用户空间中逐步消除 高危的内存安全漏漏洞洞, 并且使得剩余的攻击 面可审计 可收敛 实质性地提升 Linux 生态的安全性!12
我们在构建 MesaLock Linux 项 目的时, 使 用 Rust 语 言解决了了 内存安全的问题, 除了了语 言层 面的有点,Rust 同时提供了了 : 大量量的库 (crates) 繁荣的 生态 对于 老老旧模块的重写 但是在使 用之前, 我们需要对 Rust 有 一个深刻的理理解
内存安全? 嗯!14
什什么是 非内存安全的 Rust? 目前, 我们讨论的 Rust 的代码都能保证在编译时强制内存安全 但是,Rust 也包括另外 一个并没有强制内存安全的保证的语 言 : 非内存安全的 Rust 它和普通的 Rust 语法相同, 但 是有 一些额外的超能 力力!15
非内存安全的超能 力力 1. 解引 用原始指针 2. 访问或修改静态可变变量量 3. 调 用 非内存安全的 方法和函数 4. 实现 非内存安全的 trait!16
非内存安全的超能 力力 1. 解引 用原始指针 unsafe { let address = 0x012345usize; let r = address as *const i32; } Rust 任意读写内存地址!17
非内存安全的超能 力力 2. 访问或修改静态可变变量量 static mut COUNTER: u32 = 0; Rust fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { add_to_count(3); } unsafe { println!("counter: {}", COUNTER); } 数据竞争!18
Unsafe Superpowers 3. 调 用 非内存安全的 方法和函数 unsafe fn dangerous() { let address = 0x012345usize; let r = address as *const i32; } Rust fn main() { unsafe { dangerous(); } } 调 用 非内存安全的函数, 触发未定义的 行行为 ( 内存破坏 )!19
Unsafe Superpowers 3. 调 用外部 非内存安全的 方法和函数 extern "C" { fn abs(input: i32) -> i32; } Rust fn main() { unsafe { println!("absolute value of -3 according to C: {}", abs(-3)); } } 调 用外部 C/C++ 函数, 触发未定义的 行行为 ( 内存破坏 )!20
" 非内存安全代码 " 是不不容易易感知 Rust 开发者 : 没什什么太 大问题 至少 非内存安全的代码 需要使 用 unsafe 关键字显式的调 用 我知道哪段代码是安全 的哪段代码可能有安全问题 Me: 错 非内存安全的代码可能存在于依赖的库中 你是否有检查过依赖库的代码?!21
" 非内存安全代码 " 是不不容易易感知 Library: Rust unsafe fn dangerous() { let address = 0x012345usize; let r = address as *const i32; } fn safe_function() { unsafe { dangerous(); } } Developer: 一些库 ( 包括标准库 ) 包装了了 非内存安全的代码, 重写导出为安全的函数 fn main { safe_function(); }!22
案例例研究 : Ion Shell Ion 是 一个现代的系统 shell, 简单并强 大的语法 所有代码都由 Rust 重写编写, 提 高了了 shell 的内存安全 除此之 外, Ion shell 比 Dash 的运 行行速度要快!23
Ion shell 依赖库的关系图 ion-shell ion-shell calculate fnv glob itoa rand regex permutate failure smallvec v0.4.4 unicode-segmentation smallstring version_check xdg liner clap decimal fuchsia-zircon aho-corasick regex-syntax utf8-ranges thread_local backtrace failure_derive smallvec v0.3.3 bytecount ansi_term textwrap vec_map strsim atty serde ord_subset rustc-serialize bitflags fuchsia-zircon-sys memchr unreachable lazy_static libloading backtrace-sys cfg-if rustc-demangle synstructure termion unicode-width users winapi void cc syn redox_termios libc winapi-i686-pc-windows-gnu winapi-x86_64-pc-windows-gnu quote synom redox_syscall unicode-xid decimal libloading backtrace-sys libc cc!24
Ion Shell 中的 C 库 链接的 C 库 glibc decimal libloading backtrace-sys cc crate? 编译 C 的代码, 链接进 入 Ion shell!25
cargo build -vv 使 用 verbose 重新编译 ion shell. running: "cc" "-O0" "-ffunction-sections" "-fdata-sections" "-fpic" "-g" "-m64" "-I" "decnumber" "-Wall" "-Wextra" "- DDECLITEND=1" "-o" "/Users/mssun/Repos/ion/target/debug/ build/decimal-b8ff0faecf5447ab/out/decnumber/decimal64.o" "- c" "decnumber/decimal64.c" decimal crate: 小数浮点数数学计算库, 基于 C decnumber 库 (http://speleotrove.com/decimal/decnumber.html) Ion shell 基于浮点数数学计算库, 仍然存在潜在的内存安全问题!26
案例例研究 : rusqlite rusqlite 是 一个 Rust 库提供了了 SQLite 相关的 API 本质是 一个 SQLite API 的包装库 共计 38 个 Rust crates 直接依赖 rusqlite 200 下载量量 / 天!27
rusqlite 库的内存破坏问题 我们使 用了了 SQLite 的类型混淆 bug (CVE-2017-6991) 能够触发 Rust 库的内存破坏 Many Birds, One Stone: Exploiting a Single SQLite Vulnerability Across Multiple Software, Siji Feng, Zhi Zhou, Kun Yang, BlackHat USA 17!28
extern crate rusqlite; use rusqlite::connection; Rust fn main() { let conn = Connection::open_in_memory().unwrap(); match conn.execute("create virtual table a using fts3(b);", &[]) { //... } match conn.execute("insert into a values(x'4141414141414141');", &[]) { //... } match conn.query_row("select HEX(a) FROM a", &[], row -> String { row.get(0) }) { //... } match conn.query_row("select optimize(b) FROM a", &[], row -> String { row.get(0) }) { //... } } $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.05 secs Running `target/debug/rusqlite` success: 0 rows were updated success: 1 rows were updated success: F0634013D87F0000 [1] 31467 segmentation fault cargo run Run!29
静态链接 SQLite rusqlite 库中包含 sqlite3.c 文件 静态链接 入使 用 rusqlite 的库和 工具 没有与上游 SQLite 代码版本保持 一致!30
数据收集与研究 10,693 Rust 库 (crates.io) 2 亿下载量量 从两个 方 面分析 外部 C/C++ 库的使 用情况 usafe 关键字的使 用情况!31
外部 C/C++ 库的使 用情况 build.rs 文件 : 编译第三 方由 非 Rust 代码的编译脚本, 如 : 编译外部的 C/C++ 库 尝试编译所有收集的 Rust 库 分析编译器器的编译 log 使 用 build.rs 编译 C/C++ 源代码 静态 / 动态链接编译好的库或者系统库!32
外部 C/C++ 库的使 用情况 (>= 100) 900 825 825 675 450 468 225 272 272 222 209 190 175 168 160 112 0 crypto ssl backtrace (static) ring-core (static) ring-test (static) openssl_shim (static)!33 miniz (static) c_helper (static) dl crypto_helper (static) z curl (static)
分析 unsafe 代码 使 用 Rust 编译器器 生成 AST (abstract syntax tree) 在 AST 中找到使 用 unsafe 关键字的代码!34
unsafe 代码使 用情况 3,099 / 10,693 Rust 库 (crates) 包含 unsafe 代码 14,796 文件 651,193 行行代码!35
Fuzz Rust 库 cargo-fuzz 通过 fuzz 发现的 Use after Free 问题 :XML Document (https://github.com/shepmaster/sxd-document/issues/47) src/string_pool.rs 文件使 用了了 大量量的 unsafe 代码, unsafe 代码会破环函数中变量量 ( 数据 / 资源 ) 的所有权和 生命周 期!36
如何使 用 unsafe 代码 遵循 Rust SGX SDK 项 目中提出的混合代码内存安全架构三 原则 :: https://github.com/baidu/rust-sgx-sdk/blob/master/ documents/ccsp17.pdf 1. 隔离并模块化由 非内存安全代码编写的组件, 并最 小化其代码量量 2. 由 非内存安全代码编写的组件不不应减弱安全模块的安全性, 尤其是公共 API 和公共数据结构 3. 由 非内存安全代码编写的组件需清晰可辨识并且易易于更更新!37
一些经验 使 用 Rust!= 内存安全 小 心的使 用 非安全 Rust 记得检查依赖的使 用情况!38
总结 内存破坏和内存安全 内存安全的编程语 言 当内存安全的编程语 言变得不不再安全 外部 C/C++ 库的使 用 unsafe 关键词的使 用 如何使 用 unsafe 代码!39
欢迎提问