在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
|
# 检出最新的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
# 配置
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
# 构建!
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
|
# 检出我的分支
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
# 使用上面构建的Clang路径首先获取所需的二进制文件:clang、ld.lld和llvm-ar。
PATH="$HOME/bin/clang-release/bin:$PATH"
# 配置(这使用“defconfig”,但你可以使用“menuconfig”),但你必须
# 在make参数中包含CC和LD,否则你的.config将不知道Clang。
make defconfig CC=clang LD=ld.lld
# 启用LTO和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
# 如果你想进行运行时故障测试,启用LKDTM:
scripts/config -e CONFIG_LKDTM
# 构建!
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 0x1000
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
|
# 以root身份登录启动的系统,然后:
cat <(echo CFI_FORWARD_PROTO) >/sys/kernel/debug/provoke-crash/DIRECT
dmesg
|
以下是我在控制台上看到的CFI错误:
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。这允许内核警告但继续执行错误的调用。这对于调试很方便。在生产内核中,这将