Last month, Microsoft announced an improvement in their compiler for Visual C and C++. It now blocks some variants of the Spectre attack by inserting
LFENCE instructions to block speculative execution when it detects a pattern in the code it is compiling that is characteristic of Spectre attacks. In effect, the compiler is using the antivirus technique of looking for a "signature" of the attack and applying countermeasures when it finds one.
This approach has two main limitations. Firstly, blacklisting known attacks instead of whitelisting code patterns that are known to be safe, doesn't address the vulnerability in full generality, since an unanticipated variant of the attack that has a slightly different signature can still succeed. Secondly, it complicates debugging and maintenance, since it is much more difficult to identify, by manual inspection, cases in which the countermeasures should have been applied but weren't than to find (operationally) cases in which the countermeasures were applied even though they weren't needed.
Microsoft chose the signature-blacklisting approach because it entails less of a performance penalty: Programs compiled with the new compiler run almost as fast as the vulnerable versions produced by the old compiler. If it had been designed to insert
LFENCE instructions whenever it could not prove that the code being compiled was safe without them, the performance hit would have been much, much larger.
“Spectre Mitigations in Microsoft's C/C++ Compiler”
Paul Kocher, February 13, 2018
The author of this article created a rough benchmark to determine how much protection the signature-blacklisting approach actually provides. He took the proof-of-concept C program provided by the team that devised the Spectre attack in the first place and developed fourteen variations of the key function. Most of the variations were easy and straightforward, not to say trivial.
Microsoft's new C compiler correctly inserted
LFENCE instructions in the original proof-of-concept code and in one of the variants (in which the key instruction was replaced with a call to a function that executed the instruction and the function was then inlined). The new C compiler generated unsafe code for the other thirteen variants.