Narrow Spectre Fix

The bounds of Spectre are vague. This fix is aimed at making systems running BPF safe against version 1 of Spectre. This fix is probably peculiar to x86 but it might help elsewhere. It is peculiar to BPF and perhaps similar useful kernel hacks. Some need security at the same time as they need BPF. This fix is a hack. When a signed(!) array index N comes from a suspicious source, or is derived from suspicious data, and the array size S is known at runtime;
Replace “A[N]” by “A[((N-S)>>31) & N]”. If N is valid (N<S) then the surrogate will function as intended. If N is invalid then the surrogate will provide A[0]. In either case the narrow form of Spectre is thwarted. My understanding of x86 speculation is that the above, as compiled by any C compiler that I have used, will have no branches which Spectre depends upon. This assumes a 2’s complement machine where an int is 32 bits long. I say signed to get sign extension in the shift.

I rely here on obscure C semantics which sort of match x68 and ARM ISA semantics.

#include <stdio.h>
int main(){
for(int j = 0; j<10; ++j) printf("%d ", ((j-5)>>31) & j);
return 0;}
yields: 0 1 2 3 4 0 0 0 0 0

If the miscreant delivers N=–3 as index for an array of size 5 then he causes the machine to access A[–3]. This may depend on the way compiled code turns subscripts into offsets. This can be sidestepped by replacing “A[N]” by “A[((abs(N)-S)>>31) & N]” Clang compiles this without conditional branches. Better yet it compiles:

extern float A[100];
float get(unsigned int j){return A[j<100?j:0];}
with no conditional branches.

These alternatives avoid speculation!


Intel suggests using the LFENCE instruction to solve the same problem. Using the LFENCE operation may or may not be faster.