Clang Control Flow Integrity - Assembly Level Understanding
Published:
Compile regular llvm+clang
Following the instruction in LLVM official website[1], we can compile regular llvm+clang. Steps 4, 5, 6 can be skipped.
Note that llvm making process really eats tons of memory, so make -j with a small number. Also RELEASE build is fast and requires less memory. One example cmake configuration can be
cmake -G "Unix Makefiles" ../llvm/ \
-DLLVM_TARGETS_TO_BUILD="ARM;X86;AArch64" \
-DCMAKE_BUILD_TYPE=RELEASE \
-DCMAKE_INSTALL_PREFIX=../release
Generate LLVMgold.so
Clang CFI requires LLVM Link Time Optimization (LTO), which requires the gold ld and LLVMgold.so. You can use ld –version to check if it is gold linker or not, also use ld -plugin to check the plugin support.
1.ld.gold is usually install in /usr/bin/, we only need to make a symbolic link to let ld point to ld.gold Or as we also need to give binutils/include to llvm, so build ld.gold locally is a better choice, can follow instruction here[2]. This step will generate gold linker ld-new under build/gold/.
git clone --depth 1 git://sourceware.org/git/binutils-gdb.git binutils
mkdir binutils/build
cd binutils/build
../configure --enable-gold --enable-plugins --disable-werror
make all-gold -j4
2.Make a symbolic link to let ld point to binutils/build/gold/ld-new
sudo ln -sf binutils/build/gold/ld-new /usr/bin/ld
3.Generate LLVMgold.so, come back to llvm build fold, run
cmake -G "Unix Makefiles" -DLLVM_BINUTILS_INCDIR=PATH_TO/binutils/include ../llvm
make -j4 ENABLE_OPTIMIZED=1
Then LLVMgold.so is generated, will be loaded automatically by gold loader.
How is the CFI enforced
Clang provides CFI checks on certain levels, such as virtual table and indirect call checks. We can follow the instruction in Let’s talk about CFI: clang edition[3]. Use the indirect call source[4] as the example, we can compile it use
clang -g -fvisibility=hidden -flto -fsanitize=cfi-icall cfi_icall.c -o cfp.out -v
Clang CFI checks the dynamic type of a function pointer matching the callee function type. Function type is defined by the arguments and argument type. In other words, Clang CFI will classify all functions (address taken functions) into different sets according to their arguments and allow a function pointer to jump to any function in the correct set. Using the above example, three functions have the same arguments number and type, therefore in the same set.
static int int_arg(int arg) { ... }
static int bad_int_arg(int arg) { ... }
static int not_entry_point(int arg) { ... }
Clang will generate
00000000004008a0 int_arg:
4008a0: e9 eb fe ff ff jmpq 400790 int_arg.cfi
4008a5: cc int3
4008a6: cc int3
4008a7: cc int3
00000000004008a8 bad_int_arg:
4008a8: e9 03 ff ff ff jmpq 4007b0 bad_int_arg.cfi
4008ad: cc int3
4008ae: cc int3
4008af: cc int3
00000000004008b0 not_entry_point:
4008b0: e9 6b ff ff ff jmpq 400820 not_entry_point.cfi
4008b5: cc int3
4008b6: cc int3
4008b7: cc int3
Func Addr Bit Index 98 7654 3210
4008a0 -> 0100 0000 0000 1000 1010 0000 -> 00 for int_arg
4008a8 -> 0100 0000 0000 1000 1010 1000 -> 01 for bad_int_arg
4008b0 -> 0100 0000 0000 1000 1011 0000 -> 10 for not_entry_point
Clang CFI allows int_arg_fn
to jump to any functions in the set. From the bit index, it is easy to see that the 3rd and 4th bits combine will give functions an index. and the inserted instructions between —— in main will check if the jump-to address is in the set or not. If not, just check to 0x400785 to crash current process. This is how -fsanitize=cfi-icall is enforced.
0000000000400680 main:
400680: 55 push %rbp
400681: 48 89 e5 mov %rsp,%rbp
400684: 53 push %rbx
400685: 50 push %rax
400686: 48 89 f3 mov %rsi,%rbx
400689: 83 ff 02 cmp $0x2,%edi
40068c: 75 3e jne 4006cc main x4c=""
40068e: bf 00 0a 40 00 mov $0x400a00,%edi
400693: e8 b8 fe ff ff callq 400550 puts plt=""
400698: 48 8b 43 08 mov 0x8(%rbx),%rax
40069c: 48 0f be 38 movsbq (%rax),%rdi
4006a0: 48 8b 0c fd c0 2e 40 mov 0x402ec0(,%rdi,8),%rcx
4006a7: 00
----------------------------------------------------
4006a8: b8 a0 08 40 00 mov $0x4008a0,%eax
4006ad: 48 89 ca mov %rcx,%rdx
4006b0: 48 29 c2 sub %rax,%rdx //callee addr - base 0x4008a0
4006b3: 48 c1 c2 3d rol $0x3d,%rdx //left shift 61 bit,
//int_arg addr becomes 00
//bad_int_arg addr becomes 01
//not_entry_point addr becomes 10
4006b7: 48 83 fa 03 cmp $0x3,%rdx //compare with 3
4006bb: 0f 83 c4 00 00 00 jae 400785 //crash if >= 3, out of set
----------------------------------------------------
4006c1: 48 83 c7 d0 add $0xffffffffffffffd0,%rdi
4006c5: ff d1 callq *%rcx
.
400785: 0f 0b ud2