What Is Spectre and How to Detect It
The Spectre vulnerability dominated cybersecurity headlines. Most stories since then have covered its prevalence and how it exploits branch prediction CPUs, but not how to solve the fundamental problem.
We have released a new Klocwork checker SPECTRE.VARIANT1 that detects potential occurrences of Spectre Variant 1 (CVE-2017-5753) in your code.
In this post, we’ll:
- Provide an overview of the Spectre Variant 1 vulnerability.
- Explain why the new checker is important.
- Outline how the new checker works.
- Show how Klocwork can find the vulnerability.
What Is Spectre?
The Spectre vulnerability involves a malicious program gaining access to data that it shouldn’t have the right to see. It does so by exploiting two important techniques used to speed up computer chips called speculative execution and caching.
Why Spectre Variant 1?
Based on our research and strong customer feedback, the Klocwork development team has focused on Spectre Variant 1 for two reasons:
Intel and other organizations have already released mitigation strategies for the other variants.
Variant 1 can’t be solved by a single patch or technique — it requires the detection and resolution of multiple, specific code patterns in your source code.
You could take the brute-force approach and turn off speculative execution entirely, but this would lead to significant performance degradations in your application.
How Klocwork Detects Spectre
To understand how the Klocwork Spectre checker works, we’ll walk through this example found in the Spectre white paper (PDF). The protected data resides in an out-of-boundary memory area of:
Examining lines 14 and 15 above, the code looks generally secure. The execution of the following piece of code is protected by the bounds check seen on line 14:
secret_value = arr1->data[offset]
However, due to the possibility of speculative execution, the protection of this bounds check can be bypassed if the following conditions exist:
- The protected data has the potential to be cached in a CPU register due to speculative execution.
- The offset variable is also in the CPU cache due to speculative execution.
If these conditions are satisfied, the exploit proceeds this way:
- On line 14, offset is compared with:
- Given offset is a cache hit and arr1->length is a cache miss, the CPU requests the value of arr1->length to be loaded into the L1 cache.
- As it may take several hundred CPU cycles to load the value, the CPU may start executing line 15 speculatively.
- On line 15, the value of offset (assuming it’s been maliciously set by an attacker through a timing attack) points to the protected data and the value is read as a cache hit, becoming immediately available and stored in secret_value.
- On line 17, given the read arr2->data is a cache miss, the CPU requests the value to be loaded from the next level cache (or DRAM).
- Once the value of arr1->length arrives in L1 cache, the CPU realizes the branch was mispredicted, and execution starts along the else branch instead.
- The value of arr2->data[secret_value * 256] now arrives in the L1 cache and is exposed to an attacker via a timing attack.
How Klocwork Helps
Klocwork tracks tainted, untrusted, or otherwise suspicious data along all possible execution paths in code.
Specifically, the SPECTRE.VARIANT1 checker does two things:
Detects possible instances of tainted data. In the above example, Klocwork has flagged the input parameter offset for this reason.
Identifies suspicious code patterns. Klocwork has highlighted lines 14-15 below due to the possibility of speculative execution.
Klocwork’s Spectre Checker In Action
Now that we’ve explained the vulnerability and discussed how Klocwork’s SPECTRE.VARIANT1 checker works, we’ll walk you through a real-life example, demonstrating step-by-step how Klocwork helps you detect and remediate this serious exploit.
The code we use for our example was originally published by Erik August Johnson. (We highly encourage you to download it and try our Spectre checker for yourself.) This walkthrough uses the Klocwork Desktop Plugin for Visual Studio, but similar steps can be applied to any version of Klocwork that you have.
At the end of this post, you’ll find an in-depth video walkthrough given by one of our Klocwork developers. You’ll hear a more detailed technical explanation of this vulnerability, and see Klocwork in action detecting and eliminating this not-so-friendly ghost.
Here are our instructions on how to use Klocwork’s Spectre checker:
- Configuring the Spectre Checker.
- Open the Klocwork Solution Properties dialogue box by right-clicking a solution in the Solution Explorer.
- Then select Klocwork Solution Properties and enable SPECTRE.VARIANT1.
Locating the Spectre Vulnerability
Once the analysis is complete, you’ll see the results highlighted in the desktop editor.
In the below code, there are two conditions that indicate the potential for the Spectre vulnerability to exist:
Potentially untrusted data ‘x’ on line 42.
Untrusted data ‘x’ used in a branch that may be executed under speculative execution.
Fixing the Found Defects
As per Intel’s recommendation, the vulnerability can be remediated using the LFENCE instruction to stop speculative execution locally. Using _mm_lfence(), Intel’s compiler can implement this instruction. Inserting it before the untrusted data is used to access the array on line 43, and re-running Klocwork analysis, removes the reported defect.
Using Your Own Spectre Mitigation Function
If you have your own Spectre mitigation function, you can include a custom KB in the Klocwork analysis, identifying areas of code to the checker that you know aren’t vulnerable. To illustrate this case, we will insert the following fence instruction intrinsic function on line 45, and re-run Klocwork analysis:
The same defect as before is reported because Klocwork doesn’t recognize my_fence_intrinsic() as a remediation to the exploit. Since we know that it is, we include a custom KB file into the project (instructions here) with the following KB record:
my_fence_intrinsic – FENCE
Running the analysis again removes the reported defect once Klocwork recognizes the intrinsic function: