Control-flow Integrity (CFI)

Control-flow Integrity (CFI) attempts to validate the control-flow of a process. This is achieved by analyzing the application and building up a Control-flow Graph (CFG) representing the intended program behavior1)2).

One important addition of CFI is the so-called „shadow stack“. Next to the existing stack of an application, another one is introduced for verification purposes. Return addresses are added to both stacks upon function calls. When executing a return instruction, the top elements of both stacks is compared. In case they are equal, execution continues. But if they differ, a manipulation of the control-flow is detected.

Compiler-based Implementations

Major compilers such as GCC and LLVM support CFI. Naturally, applications need to be recompiled if CFI is added later on. Also note that the performance impact of a purely software-based solution should not be neglected3).

Following example demonstrates the usage of CFI in combination with the LLVM-powered Clang compiler4).

cfi/clang.c
// clang -fvisibility=hidden -flto -fno-sanitize-trap=all -fsanitize=cfi-icall clang.c
#include <stdio.h>
 
void admin_stuff();
void user_stuff();
 
int main()
{
    void (*fp[])() = {&admin_stuff, &user_stuff};
    int choice;
    printf("Enter choice:\n0) Admin\n1) User\n");
    scanf("%d", &choice);
    (*fp[choice])();
    return 0;
}
 
void admin_stuff()
{
    printf("admin stuff\n");
}
 
void user_stuff()
{
    printf("user stuff\n");
}

Entering valid values for the array of function pointers causes the program to behave just as expected.

$ ./a.out 
Enter choice:
0) Admin
1) User
0
admin stuff
$ ./a.out
Enter choice:
0) Admin
1) User
1
user stuff

However, entering and invalid value and trying to execute an arbitrary memory region causes the execution to abort.

$ ./a.out
Enter choice:
0) Admin
1) User
123
clang.c:13:5: runtime error: control flow integrity check for type 'void ()' failed during indirect function call
0x0000000003e8: note: (unknown) defined here

Hardware-based Implementations

Control-flow Enforcement Technology (CET)5) is a hardware-supported implementation of CFI suggested by Intel.

With CET, a shadow stack tracks function calls. Upon a call instruction, the return address is also pushed to the shadow stack. When returning from the function call, the return addresses are popped from both stacks and compared. In case of different values, an exception is raised. Additionally, targets of jmp and indirect call instructions are marked. An error occurs if these instructions are executed for a target that is not marked.

As of January 2018 the CET specification is only available as a preview and there are no processors supporting it on a hardware level. CET support is added to version 8 of GCC6).



← Back to NX bit Overview Continue with static code analysis →

1) Tice, Caroline, et al. „Enforcing Forward-Edge Control-Flow Integrity in GCC & LLVM.“ USENIX Security Symposium. 2014.
2) Conti, Mauro, et al. „Losing control: On the effectiveness of control-flow integrity under stack attacks.“ Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security. ACM, 2015.
3) Dang, Thurston HY, Petros Maniatis, and David Wagner. „The performance cost of shadow stacks and stack canaries.“ Proceedings of the 10th ACM Symposium on Information, Computer and Communications Security. ACM, 2015.