SPECTRE.VARIANT1Potential exploit of speculative executionSpectre is a hardware vulnerability discovered and published by Google in early 2018 (https://googleprojectzero.blogspot.ca/2018/01/reading-privileged-memory-with-side.html). The vulnerability results from hardware optimizations aiming at improving CPU performance while it's working with the different memory levels. Although spectre is a hardware vulnerability, for it to be exploited, certain code patterns still should be present in the software that is being run by that hardware. The original Google-published paper is available at https://spectreattack.com/spectre.pdf and shows a number of patterns exposing the vulnerability, as well as a sample program allowing to illegally access the secret data leaked via it. There are different variants of the vulnerability; the variant 1 that is excessively discussed in the original paper is of the main interest for static detection since, unlike other variants, it does not seem to be easily mitigated by general approaches and requires specific code patterns detection in the source. The variant 1 vulnerability allows to leak secret data from the victim's machine RAM via an array out-of-boundary reads. Below is the simplified code snippet demonstrating the high level overview of this vulnerability: 1 struct array { 2 unsigned long length; 3 unsigned char data[]; 4 }; 5 6 struct array *createArrayOfSize(unsigned int size); 7 8 void vulnerable_pattern(unsigned long offset) 9 { 10 struct array *arr1 = createArrayOfSize(5); /* small array */ 11 struct array *arr2 = createArrayOfSize(0x500); /* a large array */ 12 unsigned char secret_value; 13 14 if (offset < arr1->length) { /* untrusted data controls the branch */ 15 secret_value = arr1->data[offset]; /* out of boundary read in a mispredicted speculative execution */ 16 17 unsigned char value2 = arr2->data[secret_value * 256]; /* arr2->data[secret_value * 256] will be loaded to the CPU cache */ 18 /* resulting in a measurable side effect */ 19 /* some more code */ 20 } else { 21 /* code for the else branch */ 22 } 23 } The code is generally secure since 'arr1->data[offset]' read is protected by the bounds check. However, this protection can be bypassed due to the hardware vulnerability. In order for the attack to be successful, a number of conditions need to be satisfied.
While no data is still accessible directly to the attacker, as a result of this execution, they leave a side effect in the CPU, i.e., the 'arr2->data[secret_value * 256]' value has been copied into the CPU L1 cache while other elements of 'arr2->data' array are still only available from the next level caches or DRAM (if extra details of hardware memory operations are ignored). To measure this side effect and finish data leaking, the attacker only needs to read the elements of 'arr2->data' one by one (with a step of 256 and yes, it is assumed that they have some convenient ways of doing so) and measure how long each read takes (this is claimed to be something easily achieved; see Timing Attack). All the reads except for the 'arr2->data[secret_value * 256]' will be slow while the read of 'arr2->data[secret_value * 256]' will be fast, and that reveals the value of the 'secret_data'. The vulnerability can be mitigated by calling a special 'intrinsic' function anywhere in the branch before the access to 'arr2->data[secret_value * 256]' that forces the CPU to wait until all the values requested from the next level caches actually arrive in their registers. Intel compiler provides the function '_mm_lfence()' to achieve this on Intel architectures. Code exampleIn the snippet above, Klocwork reports the following defect:
code.c:17:9: [SPECTRE.VARIANT1] Potentially untrusted data 'offset' can be used to read arbitrary data beyond boundary of array 'arr1->data' at line 15 in speculative branch execution. Fixed code example
1 struct array { 2 unsigned long length; 3 unsigned char data[]; 4 }; 5 6 struct array *createArrayOfSize(unsigned int size); 7 8 void vulnerable_pattern(unsigned long offset) 9 { 10 struct array *arr1 = createArrayOfSize(5); /* small array */ 11 struct array *arr2 = createArrayOfSize(0x500); /* a large array */ 12 unsigned char secret_value; 13 14 if (offset < arr1->length) { /* untrusted data controls the branch */ 15 _mm_lfence(); /* serialization intrinsic that stops speculative execution */ 16 secret_value = arr1->data[offset]; /* no vulnerability exists so no defect is reported */ 17 18 unsigned char value2 = arr2->data[secret_value * 256]; 19 20 /* some more code */ 21 } else { 22 /* code for the else branch */ 23 } 24 } Currently, there are following fence operations supported:
function_name - FENCE The above record will be interpreted by the checker that function 'function_name' will cause the CPU to wait until the cache is synchronized with the RAM state or make the data leakage in speculative executive a non-issue in any other way. Analysis along that path will be stopped. |