A program runs like a path through a map. Each step follows a road that the programmer drew. Control Flow Integrity is a guard that checks the roads are still the ones that were meant to be used. It tries to stop attackers from forcing the program to take a wrong turn. This idea has grown from a simple check to a common part of modern software defence.
How programs normally move
In a normal run, code moves in a predictable order. A function is called, work is done, and then the program returns to where it came from. Loops repeat a block until a condition changes, and branches choose one of two paths based on a test. These moves are decided at compile time and then followed at run time. The processor keeps a small invisible stack that remembers where to go back. When everything is clean, the flow is smooth and the program does what it was written to do.
The difficulty comes with indirect moves. A virtual method call, a function pointer, or a jump table does not have a fixed address until the program is running. The compiler knows the set of possible targets, but the choice is made later. This flexibility is useful for libraries, plugins and optimisations. It also means the path is partly decided by data that lives in memory. If that data can be changed, the path can be changed too.
The risk of hijacked paths
Attackers look for places where memory can be overwritten. A buffer overflow, a use after free, or a corrupted object can let an attacker write new values into the program. Once a return address or a function pointer is changed, the next move is no longer in the programmer’s map. The program can be forced to run code that was never meant to run, or to run existing code in a new order. This return oriented style of attack reuses small pieces of legitimate code to build malicious behaviour.
For many years defences focused on making it harder to overwrite memory. Canaries, address space layout randomisation and non executable pages helped, but they did not check the direction of travel itself. An attacker who finds a way to change a pointer could still steer execution to a chosen target. That is why the idea of checking the control flow itself became important. The question changed from “can memory be changed” to “is the program still following allowed roads”.
How integrity is enforced
Control Flow Integrity builds a map of allowed targets for every indirect branch and return. At compile time the compiler records which functions can call which functions, and where a return is allowed to go. At run time a check is done before each sensitive move. If the target is not on the list, the program stops. Early forms used a simple list of valid return addresses. Later designs used forward edge checks for calls and indirect jumps, and backward edge checks for returns.
Modern systems use hardware help to make the checks cheap. Some processors provide pointer authentication, which signs return addresses with a secret key. Others provide branch target identification that restricts indirect jumps to a small set of possible locations. Software schemes insert small guards around each call and return, and keep a shadow stack separate from the normal stack. The shadow stack holds clean return addresses and is compared with the real one on each return. The cost is lower than it once was, and the protection is stronger because the check happens on every move.
Trade offs in real systems
No defence is free. Checks add instructions and can increase code size and memory use. On some workloads the slowdown is small, on others it is noticeable. Compilers must be able to build accurate graphs of allowed targets, which can be hard with dynamic code generation or highly reflective languages. Legacy binaries without source code are harder to protect, and some systems still rely on partial solutions.
Despite the costs, Control Flow Integrity has become a practical layer of defence. It does not stop memory corruption, but it limits what an attacker can do with it. The program may still be wounded, but it cannot easily be steered to run arbitrary commands. For a global audience building software for phones, browsers and embedded devices, this limited but reliable guarantee matters. It changes the economics of an attack, forcing more work for less reward, and it fits with other defences in a layered approach.