在Linux上游中实验Clang CFI:构建与调试指南

本文详细介绍了如何在x86架构的上游Linux内核中启用和实验Clang的控制流完整性(CFI)功能,包括编译环境配置、内核构建步骤、常见警告处理以及CFI故障测试方法。

在Linux上游中实验Clang CFI

虽然内核控制流完整性(CFI)的许多工作集中在arm64(因为内核CFI在Android上可用),但很大一部分是在核心内核本身(尤其是构建系统)中。最近我在x86上获得了合理的构建和启动,并启用了所有功能,我一直在梳理一些剩余的部分。我认为现在是记录我所有操作的好时机,以便其他人想要尝试并发现需要修复的问题。

首先,所有内容都基于Sami Tolvanen对Clang前向边缘CFI的上游移植,其中包括他的链接时优化(LTO)工作,这是CFI所必需的。这个树还包括他在arm64上使用Clang的阴影调用栈(SCS)进行的后向边缘CFI工作。

除此之外,我有一些x86特定的补丁,使我能够启动内核而不会在控制台上出现大量警告。此外,还有通用的链接器脚本清理、CFI转换修复和x86加密修复,所有这些都处于不同的上游化状态。生成的树在这里。

在编译器方面,你需要一个非常新的Clang和LLD(即“Clang 10”,或者像我一样从最新的git构建)。例如,以下是开始的方法。首先,检出、配置和构建Clang(如果你想要完整的git历史,请省略“–depth=1”):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Check out latest LLVM
mkdir -p $HOME/src
cd $HOME/src
git clone --depth=1 https://github.com/llvm/llvm-project.git
mkdir -p llvm-build
cd llvm-build
# Configure
mkdir -p $HOME/bin/clang-release
cmake -G Ninja \
      -DCMAKE_BUILD_TYPE=Release \
      -DLLVM_ENABLE_PROJECTS='clang;lld;compiler-rt' \
      -DCMAKE_INSTALL_PREFIX="$HOME/bin/clang-release" \
      ../llvm-project/llvm
# Build!
ninja install

然后检出、配置和构建CFI树。(这假设你已经有了Linus的树的检出。)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Check out my branch
cd ../linux
git remote add kees https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git
git fetch kees
git checkout kees/kspp/cfi/x86 -b test/cfi
# Use the above built Clang path first for the needed binaries: clang, ld.lld, and llvm-ar.
PATH="$HOME/bin/clang-release/bin:$PATH"
# Configure (this uses "defconfig" but you could use "menuconfig"), but you must
# include CC and LD in the make args or your .config won't know about Clang.
make defconfig CC=clang LD=ld.lld
# Enable LTO and CFI.
scripts/config \
     -e CONFIG_LTO \
     -e CONFIG_THINLTO \
     -d CONFIG_LTO_NONE \
     -e CONFIG_LTO_CLANG \
     -e CONFIG_CFI_CLANG \
     -e CONFIG_CFI_PERMISSIVE \
     -e CONFIG_CFI_CLANG_SHADOW
# Enable LKDTM if you want runtime fault testing:
scripts/config -e CONFIG_LKDTM
# Build!
make -j$(getconf _NPROCESSORS_ONLN) CC=clang LD=ld.lld

不要被各种警告吓到,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ld.lld: warning: cannot find entry symbol _start; defaulting to 0x10000
llvm-ar: error: unable to load 'arch/x86/kernel/head_64.o': file too small to be an archive
llvm-ar: error: unable to load 'arch/x86/kernel/head64.o': file too small to be an archive
llvm-ar: error: unable to load 'arch/x86/kernel/ebda.o': file too small to be an archive
llvm-ar: error: unable to load 'arch/x86/kernel/platform-quirks.o': file too small to be an archive
WARNING: EXPORT symbol "page_offset_base" [vmlinux] version generation failed, symbol will not be versioned.
WARNING: EXPORT symbol "vmalloc_base" [vmlinux] version generation failed, symbol will not be versioned.
WARNING: EXPORT symbol "vmemmap_base" [vmlinux] version generation failed, symbol will not be versioned.
WARNING: "__memcat_p" [vmlinux] is a static (unknown)
no symbols

根据需要调整你的.config(但再次确保CC和LD参数分别指向Clang和LLD)。这应该(!)导致一个愉快的可启动x86 CFI启用内核。如果你想看到CFI失败的样子,你可以戳LKDTM:

1
2
3
# Log into the booted system as root, then:
cat <(echo CFI_FORWARD_PROTO) >/sys/kernel/debug/provoke-crash/DIRECT
dmesg

以下是我在控制台上看到的CFI splat:

1
2
3
4
5
6
7
8
9
[   16.288372] lkdtm: Performing direct entry CFI_FORWARD_PROTO
[   16.290563] lkdtm: Calling matched prototype ...
[   16.292367] lkdtm: Calling mismatched prototype ...
[   16.293696] ------------[ cut here ]------------
[   16.294581] CFI failure (target: lkdtm_increment_int$53641d38e2dc4a151b75cbe816cbb86b.cfi_jt+0x0/0x10):
[   16.296288] WARNING: CPU: 3 PID: 2612 at kernel/cfi.c:29 __cfi_check_fail+0x38/0x40
...
[   16.346873] ---[ end trace 386b3874d294d2f7 ]---
[   16.347669] lkdtm: Fail: survived mismatched prototype function call!

“Fail: survived …”的声明是由于CONFIG_CFI_PERMISSIVE=y。这允许内核警告但继续执行错误的调用。这对于调试很方便。在生产内核中,这将被移除,并且违规的内核线程将被杀死。如果你在禁用配置的情况下再次运行此操作,LKDTM将不会继续。:)

享受吧!如果你能在我之前弄清楚为什么KPTI入口处理程序中仍然有CFI检测,请告诉我并帮助我们修复它。;)

© 2019 – 2020, Kees Cook. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 License.

评论 (2)

2 条评论

ashimida 说: 你好Kees,感谢文章! 我想问一下Linux内核是否有计划在未来支持基于gcc的CFI,或者如果我想使用CFI,是否必须使用clang编译内核。 最好的问候! 评论由 ashimida — 2019年11月20日 @ 10:21 pm

kees 回复: 我不知道gcc中有CFI实现。希望这能很快改变,但目前只有Clang。 评论由 kees — 2019年11月24日 @ 4:26 am

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计