Several decades of research has netted us a wide variety of defenses against the exploitation of memory errors, yet no silver bullet. Multi-Variant Execution (MVX) is one promising avenue with the ability to defend against a wide range of attacks, both known and unknown. MVX systems facilitate this by running multiple variants of the same target application in parallel while feeding them the same inputs. By carefully choosing automated diversification techniques to construct these variants, we can ensure they will behave identically under benign inputs, but diverge when attacked. Such divergences can then be detected to thwart attacks. Unfortunately, MVX still falls short of its promises for a few reasons, limiting its real-world adoption. For one, MVX systems require that all input to a target application is replicated and fed to all variants, which is commonly done at the system call level. Shared memory breaks this assumption, allowing variants to map memory pages that are readable and writable from different processes through unprivileged instructions. This allows for inter-process communication or efficient file access that evades the monitor's control. State-of-the-art MVX systems either ignore shared memory or explicitly prevent the variants from setting it up. Our analysis of modern applications, ranging from web servers and browsers to media players, shows that many applications rely on the availability of shared memory, either for advanced features or even basic functionality. Even more, while most have some alternative implementation based on system calls available, others no longer provide any alternatives. It then follows that modern applications cannot be fully protected by MVX systems unless shared memory accesses are correctly intercepted to be checked and replicated. Denying applications access to shared memory is no longer a viable long-term solution for MVX systems to take. Based on these findings from our analysis, we identify key requirements to support access to shared memory and construct a two-layered design. The first layer utilizes page faults to ensure all accesses to shared memory will be intercepted, but is relatively slow. We utilize this first layer to identify any instructions that access shared memory and instrument those instructions to call into the second layer, an in-process agent that we inject into the variants to handle the accesses more efficiently. Careful design allows for both layers to work in tandem, preventing any uninstrumented accesses from escaping and keeping the in-process handling secure. Further, several problems are fundamental to the design of MVX. First, they often require access to source code to construct variants and eliminate non- deterministic program behavior. Second, MVX systems run multiple variants in parallel, which linearly scales its resource overhead with the number of variants. We propose a solution that limits MVX to only certain parts of a target application and runs the rest in a lighter Single-Variant Execution (SVX) mode, keeping all but one variant in a stopped state. This solution, dubbed Partial Multi-Variant Execution (PMVX), relaxes the requirement for source code and reduces the resource consumption for the parts of the application executing in SVX. When switching from SVX to MVX though, the variants will likely be in diverging states as the leader has executed code the followers have not, thus requiring us to address the problem of bringing the different variants back in equivalent states. We combine two strategies to efficiently perform this state migration. First, for large memory regions that are not diversified under disjoint code layouts, a common limitation in MVX systems due to the existence of address sensitive behavior, we copy them from the leader variant to the followers by making copy-on-write copies through a custom kernel module. Second, for smaller values that are part of mappings we cannot simply copy directly, we use handlers injected into the variants to copy those values to a one-way channel between the leader and the followers and out of this channel to their equivalent location in the followers. Additionally, we reuse this second strategy to move variant-specific values, i.e. pointers, from leader to followers and translate them in the process. We implemented PMVX in a proof-of-concept system and evaluated the security and performance characteristics in detail. From this evaluation we can conclude that PMVX shows great potential, but requires careful configuration to keep the overhead of the state migration from outweighing any benefits. With these two solutions we both enable MVX systems to support a wider range of applications with the added support of handling shared memory and pave the way for MVX systems to tackle their inherent overhead cost caused by running multiple variants in parallel. Careful design choices allow us to achieve this while maximally upholding the security guarantees provided by MVX.
Jonas Vinck (Wed,) studied this question.