What is CFI?
Control flow integrity (CFI) is a security mechanism that disallows changes to the original control flow graph of a compiled binary, making it significantly harder to perform such attacks. In Android 9, we enabled LLVM’s implementation of CFI in more components and also in the kernel. System CFI is on by default, but you need to enable kernel CFI. Support for kernel CFI exists in Android common kernel versions 4.9 and 4.14. For Android P, CFI is enabled by default widely within the media frameworks and other security-critical components, such as NFC and Bluetooth. CFI kernel support has also been introduced into the Android common kernel when building with LLVM, providing the option to further harden the trusted computing base.
Other important reference materials:
Control Flow Integrity Design Documentation
Compiler-based security mitigations in Android P
Build a kernel with CFI
- create a standalone toolchain
I created a toolchain of type arm64:
$NDK/build/tools/make_standalone_toolchain.py \ --arch arm64 --api 28 --install-dir /tmp/my-arm64-toolchain
- download goldfish source code skip. You can refer here
- enable CFI in defconfig
- copy defconfig(goldfish/arch/arm64/configs/) to cfi_defconfig.
- add these lines:
CONFIG_LTO_CLANG=y CONFIG_CFI_CLANG=y
- make sure your swap file > 10GB
# Make all swap off sudo swapoff -a # Resize the swapfile sudo dd if=/dev/zero of=/swapfile bs=1M count=10240 # Make swapfile usable sudo mkswap /swapfile # Make swapon again sudo swapon /swapfile
- build kernel with cfi
PATH=$PATH:/path/to/my-arm64-toolchain/bin/ export CROSS_COMPILE=aarch64-linux-android- export ARCH=arm64 make CC="/path/to/my-arm64-toolchain/bin/clang" CLANG_TRIPLE=aarch64-linux-gnu-
Please be noticed that, if missing LLVMgold.so and libstd++.so.1, please copy these 2 files into /usr/lib from your toolchain.
- vmlinxu with CFI ON
After successfully build, vmlinux can be found here .
CFI in Android
overview
There are 3 major CFI check types:
- Forward-Edge CFI for Virtual Calls
- Forward-Edge CFI for Indirect Function Calls
- Backward-edge CFI for return statements (RCFI)
CFI will do runtime check in the above 3 type positions. In current Android kernel(4.9), we will investigate the first 2 types.
insight
Android kernel CFI(Forward-Edge CFI) consists of two parts: fast path check and slow path check. See the following picture as an example.
This picture illustrates how a function that takes an object and calls a virtual function gets translated into assembly with and without CFI. Actually each check point(foo function here) has its own instrumented unique range value. Another example from my compiled vmlinux:
The above picture describes the cfi implementation in Android kernel. It also includes fast path check and slow path check. Fast path check determines if the pointer falls within an expected range of addresses of compatible vtables. Failing that, execution falls through to a slow path that does a more extensive check for valid classes that are defined in other shared libraries. The slow path will abort execution if the vtable pointer points to an invalid target.
The relationship between fast check and slow check can be found here:
In the monolithic scheme a call site is instrumented as
if (!InlinedFastCheck(f))
abort();
call *f
In the cross-DSO scheme it becomes
if (!InlinedFastCheck(f))
__cfi_slowpath(CallSiteTypeId, f);
call *f
CallSiteTypeId is the id
you can see in the above picture. It is for slowpath check. Fast path check is simple which just check the alignment and range. The above picture equals to the following:
v4 is a function pointer in array fn_handler, e.g. fn_show_ptregs which locates at 0xFFFF000008E322BC, dummycon_deinit is a value of 0xFFFF000008E322A0. So v4-dummycon_deinit = 0xFFFF000008E322BC - 0xFFFF000008E322A0 = 0x1C, which satisfy both alignment and range check.
If failed, slowpath check works. Slow path check is implemented in kernel as:
void __cfi_slowpath(uint64 CallSiteTypeId, void *TargetAddr)
CallSiteTypeId is a 8 bytes long number which is introduced before. This function loads a shadow value for TargetAddr,finds the address of __cfi_check and calls that. The default __cfi_check in Android kernel is a very big function contains lots of valid compatible addresses. See __cfi_check as below:
.text:FFFF00000808C000 EXPORT __cfi_check
.text:FFFF00000808C000 __cfi_check
.text:FFFF00000808C000 MOV X8, #0xF484 ;start locating the entry by
.text:FFFF00000808C004 MOVK X8, #0x3DDE,LSL#16 ;CallSiteTypeId
.text:FFFF00000808C008 MOVK X8, #0xB025,LSL#32
.text:FFFF00000808C00C MOVK X8, #0xFEAD,LSL#48
.text:FFFF00000808C010 CMP X0, X8
.text:FFFF00000808C014 B.GT loc_FFFF00000808C15C
.text:FFFF00000808C018 MOV X8, #0xC424
.text:FFFF00000808C01C MOVK X8, #0xBC96,LSL#16
.text:FFFF00000808C020 MOVK X8, #0xC27D,LSL#32
.text:FFFF00000808C024 MOVK X8, #0xBED0,LSL#48
.text:FFFF00000808C028 CMP X0, X8
.text:FFFF00000808C02C B.LE loc_FFFF00000808C2A0
.text:FFFF00000808C030 MOV X8, #0xB27D
.text:FFFF00000808C034 MOVK X8, #0xAE66,LSL#16
.text:FFFF00000808C038 MOVK X8, #0x6293,LSL#32
.text:FFFF00000808C03C MOVK X8, #0xDDBE,LSL#48
.text:FFFF00000808C040 CMP X0, X8
.text:FFFF00000808C044 B.GT loc_FFFF00000808C4F8
.text:FFFF00000808C048 MOV X8, #0xB0BA
.text:FFFF00000808C04C MOVK X8, #0x3794,LSL#16
.text:FFFF00000808C050 MOVK X8, #0x2CA1,LSL#32
.text:FFFF00000808C054 MOVK X8, #0xCDC9,LSL#48
.text:FFFF00000808C058 CMP X0, X8
.text:FFFF00000808C05C B.GT loc_FFFF00000808CB40
.text:FFFF00000808C060 MOV X8, #0x90FF
.text:FFFF00000808C064 MOVK X8, #0xD5B2,LSL#16
.text:FFFF00000808C068 MOVK X8, #0x5612,LSL#32
.text:FFFF00000808C06C MOVK X8, #0xC656,LSL#48
.text:FFFF00000808C070 CMP X0, X8
.text:FFFF00000808C074 B.LE loc_FFFF00000808D128
.text:FFFF00000808C078 MOV X8, #0x1A37
.text:FFFF00000808C07C MOVK X8, #0x99F4,LSL#16
.text:FFFF00000808C080 MOVK X8, #0xEDB0,LSL#32
.text:FFFF00000808C084 MOVK X8, #0xC9F0,LSL#48
.text:FFFF00000808C088 CMP X0, X8
.text:FFFF00000808C08C B.GT loc_FFFF00000808DF68
.text:FFFF00000808C090 MOV X8, #0x5126
.text:FFFF00000808C094 MOVK X8, #0x3180,LSL#16
.text:FFFF00000808C098 MOVK X8, #0x6735,LSL#32
.text:FFFF00000808C09C MOVK X8, #0xC836,LSL#48
.text:FFFF00000808C0A0 CMP X0, X8
.text:FFFF00000808C0A4 B.LE loc_FFFF00000808F8E8
.text:FFFF00000808C0A8 MOV X8, #0xC2AA
.text:FFFF00000808C0AC MOVK X8, #0x7417,LSL#16
.text:FFFF00000808C0B0 MOVK X8, #0xE1FC,LSL#32
.text:FFFF00000808C0B4 MOVK X8, #0xC923,LSL#48
.text:FFFF00000808C0B8 CMP X0, X8
.text:FFFF00000808C0BC B.LE loc_FFFF0000080925E8
.text:FFFF00000808C0C0 MOV X8, #0x788E
.text:FFFF00000808C0C4 MOVK X8, #0x571D,LSL#16
.text:FFFF00000808C0C8 MOVK X8, #0xACB8,LSL#32
.text:FFFF00000808C0CC MOVK X8, #0xC989,LSL#48
.text:FFFF00000808C0D0 CMP X0, X8
.text:FFFF00000808C0D4 B.LE loc_FFFF0000080973F4
.text:FFFF00000808C0D8 MOV X8, #0xC19A
.text:FFFF00000808C0DC MOVK X8, #0xDF3A,LSL#16
.text:FFFF00000808C0E0 MOVK X8, #0xD926,LSL#32
.text:FFFF00000808C0E4 MOVK X8, #0xC9BC,LSL#48
.text:FFFF00000808C0E8 CMP X0, X8
.text:FFFF00000808C0EC B.GT loc_FFFF00000809F824
.text:FFFF00000808C0F0 MOV X8, #0x89EA
.text:FFFF00000808C0F4 MOVK X8, #0xA29C,LSL#16
.text:FFFF00000808C0F8 MOVK X8, #0x53E6,LSL#32
.text:FFFF00000808C0FC MOVK X8, #0xC9A8,LSL#48
.text:FFFF00000808C100 CMP X0, X8
.text:FFFF00000808C104 B.GT loc_FFFF0000080AD054
.text:FFFF00000808C108 MOV X8, #0x2763
.text:FFFF00000808C10C MOVK X8, #0x5163,LSL#16
.text:FFFF00000808C110 MOVK X8, #0x85A1,LSL#32
.text:FFFF00000808C114 MOVK X8, #0xC9A3,LSL#48
.text:FFFF00000808C118 CMP X0, X8
.text:FFFF00000808C11C B.GT loc_FFFF0000080C20D0
.text:FFFF00000808C120 MOV X8, #0x788F
.text:FFFF00000808C124 MOVK X8, #0x571D,LSL#16
.text:FFFF00000808C128 MOVK X8, #0xACB8,LSL#32
.text:FFFF00000808C12C MOVK X8, #0xC989,LSL#48
.text:FFFF00000808C130 CMP X0, X8
.text:FFFF00000808C134 B.EQ loc_FFFF0000080DD5F0
.text:FFFF00000808C138 MOV X8, #0x8829
.text:FFFF00000808C13C MOVK X8, #0xF209,LSL#16
.text:FFFF00000808C140 MOVK X8, #0x19E0,LSL#32
.text:FFFF00000808C144 MOVK X8, #0xC999,LSL#48
.text:FFFF00000808C148 CMP X0, X8
.text:FFFF00000808C14C B.NE loc_FFFF0000080DD5FC
.text:FFFF00000808C150 ADRP X8, #get_cpu_device@PAGE ;start checking if
.text:FFFF00000808C154 ADD X8, X8, #get_cpu_device@PAGEOFF ;TargetAddr equals with
.text:FFFF00000808C158 B loc_FFFF00000810C6AC ;get_cpu_device
...
...
...
.text:FFFF00000810C1FC locret_FFFF00000810C1FC
.text:FFFF00000810C1FC
.text:FFFF00000810C1FC RET
...
...
...
.text:FFFF00000810C6AC loc_FFFF00000810C6AC
.text:FFFF00000810C6AC
.text:FFFF00000810C6AC CMP X1, X8
.text:FFFF00000810C6B0 B.EQ locret_FFFF00000810C1FC
.text:FFFF00000810C6B4
.text:FFFF00000810C6B4 loc_FFFF00000810C6B4
.text:FFFF00000810C6B4
.text:FFFF00000810C6B4 MOV X0, X2
.text:FFFF00000810C6B8 BL __cfi_check_fail
.text:FFFF00000810C6B8 ; End of function __cfi_check
It firstly uses id to search the proper entry and then check if the entry’s function(get_cpu_device) equals with TargetAddr. If not equal, CFI will break the system.