|Industry-wide severity ratings can be found in the National Vulnerability Database|
Some processors use an assist1 to implement certain operations that have floating-point semantics. If an adversary can control the inputs to these operations, then the adversary may be able to influence the transient execution of dependent operations in a manner that can cause specific data to be inferable via a microarchitectural covert channel. This method for a transient execution attack is called Floating Point Value Injection (FPVI). It has been assigned CVE-2021-0086 with a base score of CVSS 6.5 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N. Refer to the Affected Processors table for a list of processors affected by FPVI.
Some processors may produce a transient (non-permanent) result for floating-point operations that require an assist to generate the result for certain inputs. For example, a denormal/subnormal floating-point result can only be produced by an assist. Before the assist is triggered, the processor may execute some dependent operations using a transient result that may not match the correct result. When the assist is triggered, the processor flushes the pipeline and restarts execution to obtain the correct result. However, the dependent operations that execute transiently prior to the flush may permit program data to be inferred through a covert channel.
An FPVI attack requires the adversary to identify a disclosure gadget2 that uses the floating-point result to access a secret and then transmit the secret over a covert channel. For example, the disclosure gadget could use the floating-point result to compute a load address, thus allowing the adversary to choose which program data to load. Alternatively, if the result of a floating-point operation is used to determine an indirect branch target and the adversary controls the inputs to that floating-point operation, then the adversary may be able to redirect transient execution to a disclosure gadget elsewhere in the program.
The following list summarizes several conditions that must be met for an FPVI adversary to be able to infer an adversary-chosen secret from memory:
- The adversary must be able to control at least one floating-point input to a floating-point operation that may force an assist to generate the result.
- The floating-point result must be used to do at least one of the following:
- Compute a memory address that is not subjected to bounds checking or masking (for example, if the floating-point result is cast by the program into a memory address).
- Determine the type of a sequence of bits that may or may not be used as a pointer, depending on the type check.
- The memory address computed in step 2 must be used to do at least one of the following:
- Load a program secret from memory; this secret must then be consumed by a dependent operation that can transmit the secret over a covert channel (for example, this dependent operation could be a memory access that would use the secret to form its load/store address). These respective load and transmit operations would form a disclosure gadget.
- Form a branch target for an indirect call/jump operation; the adversary can then redirect the instruction pointer to a disclosure gadget located elsewhere in the program.
- Steps 2 and 3 above must be able to execute within the transient execution window created by the floating-point assist.
Alternatively, if a secret value resides in a register prior to an assisting floating-point operation, then that secret could be inferable if the adversary is able to satisfy all conditions described in steps 1, 2, 3.1, and 4 for that floating-point operation.
Given that memory addresses are whole numbers and array indexes are integers, it is not common for floating-point values to be used to directly access memory in most programs and most programming languages, as required in condition 2.1 above. However, some dynamically typed languages may use floating-point values to encode tagged data that could represent, for example, an integer or a pointer, depending on the tag, which could satisfy condition 2.2.
Not-a-Number (NaN) boxing is one dynamic typing technique that has been used by commodity Just-in-Time (JIT) compilers to wrap dynamically typed data in a 64-bit (double precision) NaN floating-point value. Normal floating-point values are encoded as ordinary doubles; all non-floating-point values are encoded into the low-order 51 bits of a NaN value, possibly with a type tag. By convention, NaN values produced by floating-point operations are expected to have the low-order 51 bits all set to zero. Hence, it should not be possible for floating-point computations to yield results that collide with arbitrary values encoded by program logic into the low-order 51 bits of a NaN value. NaN boxing uses this convention to establish safe dynamic typing. However, this convention may not hold for transient execution when a floating-point operation requires an assist to produce the correct result (for example, if that result is denormal/subnormal). The transient result may evaluate to a NaN whose low-order 51 bits are not all zero, and thus may be interpreted during transient execution as a non-floating-point type, such as a pointer. Therefore, code may be impacted by FPVI if it relies on this specific convention of NaN floating-point operations.
There are several alternative techniques that can be used to mitigate FPVI.
Managed runtimes impacted by FPVI should adhere to Intel’s current guidance3 for managed runtime security. In general, isolating mutually distrusting code across the process boundary suffices to mitigate both in-domain4 Spectre and in-domain FPVI.
Constraining transient floating-point values
If a managed runtime uses NaN boxing and does not adhere to Intel’s current guidance for managed runtime security, the CMOVcc instruction can be used to supplement dynamic type checks by constraining the transient value so that it must adhere to the NaN-boxing convention. For example:
DIVSD XMM0, XMM1 MOVQ RAX, XMM0 MOVQ RBX, 0xFFF8000000000000 CMP RAX, RBX CMOVA RAX, RBX
Lines 1-2 perform a floating-point division and copy the result into RAX. Line 4 compares the result in RAX to the largest floating-point NaN value allowed by the convention discussed above (for 64-bit floating-point types, this value is 0xFFF8000000000000). If the result in RAX is greater than this value, then the division result does not adhere to the convention and therefore must be transient. To prevent this transient result from propagating further, the CMOVA instruction in line 5 will overwrite the transient result with a benign value that the adversary cannot arbitrarily manipulate, for example, the largest NaN.
Setting FTZ and DAZ
Code that does not require denormal/subnormal precision can set the FTZ and DAZ flags to 1. When both of these flags are set, floating-point operations will not require an assist to produce the correct result.
Mitigating gadgets with LFENCE
Any specific gadget that satisfies the FPVI requirements outlined in the Background section can be mitigated by placing an LFENCE instruction either after the floating-point operation, or before the load/store/branch that is influenced by the output of that floating-point operation.
Code with existing Load Value Injection (LVI) mitigations
- Assists are conditions that are handled internally by the processor and thus do not require software involvement. While both faults and assists may cause the results of a μop to be discarded, assists restart and complete the instruction without needing software involvement, whereas faults do need software involvement (for example, an exception handler). For example, setting the Dirty bit in a page table entry may be done using an assist.
- Refer to Refined Speculative Execution Terminology.
- Refer to Managed Runtime Speculative Execution Side Channel Mitigations.
- Refer to Refined Speculative Execution Terminology.
- For example, the code is compiled with LVI mitigation by clang with -mlvi-hardening, or by MSVC with /Qspectre-load, or by GCC with -Wa,-mlfence-after-load=yes -Wa,-mlfence-before-indirect-branch=memory.