A couple of years ago, I wrote and updated our MASM codebase with this macro below to combat Spectre V2.
NOSPEC_JMP MACRO target:REQ
PUSH target
JMP x86_indirect_thunk
ENDM
NOSPEC_CALL MACRO target:REQ
LOCAL nospec_call_start
LOCAL nospec_call_end
JMP nospec_call_end
ALIGN 16
nospec_call_start:
PUSH target
JMP x86_indirect_thunk
ALIGN 16
nospec_call_end:
CALL nospec_call_start
ENDM
.CODE
;; This is a special sequence that prevents the CPU speculating for indirect calls.
ALIGN 16
x86_indirect_thunk:
CALL retpoline_call_target
;; No benefit from aligning the capture_speculation branch target, as it is only potentially speculatively executed.
capture_speculation:
PAUSE
JMP capture_speculation
ALIGN 16
retpoline_call_target:
IFDEF WIN64
LEA RSP,[RSP 8]
ELSE
LEA ESP,[ESP 4]
ENDIF
RET
For example, here's some assembly code with speculation enabled (MST_QSPECTRE=1)
main PROC NEAR C
PUSH ESI
PUSH EDI
PUSH EBX
PUSH EBP
MOV EAX,OFFSET MyFun
;; Generated code to Call an indirect pointer without speculation.
IFDEF MST_QSPECTRE
NOSPEC_CALL EAX
ELSE
CALL EAX
ENDIF
POP EBP
POP EBX
POP EDI
POP ESI
RET
main ENDP
The disassembly shows how the speculative instructions are inserted
Question
In 2021, can I safely remove that MASM macro and rely upon CPU microcode updates etc.,... to resolve any Spectre concerns? For C code, looks like M$ and Linux C compilers have resolved it:
- Microsoft added /Qspectre to their MSVC compiler.
- GCC has compiler options for mitigating Spectre v2 via -mfunction-return, etc.,...
Thanks.
CodePudding user response:
Those compiler options work by generating special asm, whether it's retpolines or lfence
or whatever. When you're writing asm by hand, obviously it's still up to you whether to manually include special asm or not.
Changes to OSes are the relevant thing for you. The OS, on a CPU with updated microcode, can defend you from other threads by telling the CPU not to allow branch history from past code to influence future code. (The ability to ask it to do this was added in microcode updates, and usually works by just flushing the branch prediction caches).
Another software thread executing on the other logical core of the same physical core can "attack" your code on most CPUs, because branch predictors are shared. At least in theory; ASLR might make that implausible if both tasks would need to be using the same virtual addresses for their branch targets to prime the predictors.
So in user-space, I think you only need to defend yourself from Spectre if you're worried about code running in the same thread (e.g. a JIT engine running untrusted code inside a browser or JVM has to defend itself) or on the same physical core.