There are many embedded systems on which we rely heavily in our day to day lives, and for these it is crucial to ensure that these systems are as robust as possible. To this end, it is important to have strong guarantees about the integrity of running code. Achieving this naturally requires close integration between hardware features and compiler toolchain support for these features.
To achieve this, one Embecosm customer’s architecture uses hardware signing to ensure integrity of a program’s control flow. Each instruction’s interpretation depends on the preceding instruction in the execution flow (and hence the sequence of all preceding instructions). Basic blocks require a “correction value” to bring the system into a consistent state
when arriving from different predecessors.
Compiler support is needed so that compiled code can receive the benefits of this feature. During 2016 we implemented the infrastructure for this feature which can be enabled on a per-function level in LLVM, for functions written in C and/or assembly.
We extended the target’s backend with a pass that produces metadata describing a system’s control flow. This allows branches and calls to be resolved with appropriate correction values. A particular challenge was dealing with function pointers and hence indirect transfers of control. C attributes were implemented to support such functionality in the LLVM front end.
The encoding of each instruction, and the correction values cannot be finally determined until the final programs is linked. Using the metadata generated by LLVM, we can recreate the control flow graph for the entire program. From this, each instruction can be signed, and the correction values for each basic block inserted into the binary.
The full system was demonstrated at the 2016 LLVM Developer’s meeting in San Jose, California.