Closed Bug 1438886 Opened 7 years ago Closed 7 years ago

Spectre JIT mitigation for speculated data after the execution of C++ calls.

Categories

(Core :: JavaScript Engine: JIT, defect, P1)

defect

Tracking

()

RESOLVED FIXED
mozilla60
Tracking Status
firefox60 + fixed

People

(Reporter: nbp, Assigned: nbp)

References

Details

Attachments

(1 file, 2 obsolete files)

As the C++ code is not yet being instrumented to mitigate spectre v1 attacks, we can add mitigations to prevent speculative execution across the C++ call boundary when coming from the JIT. One idea to do these mitigations is to use lfence instruction, as suggested by Intel and AMD.
Assignee: nobody → nicolas.b.pierron
Status: NEW → ASSIGNED
Priority: -- → P1
This adds lfence instruction right before the call instructions into C++ code for x86 and x64. If there is a regression it should be below the noise level on my computer.
Attachment #8952394 - Flags: review?(jdemooij)
Blocks: 1439940
Sorry for the delay. Some questions: (1) I think it's more (or at least as) important to add an LFENCE *after* the call, since most attacks use an out-of-bounds load and that's easy to do with DataView.prototype.getInt32 or a similar VMFunction. We would only do this for VMFunctions that return a value. (2) How much does this affect a worst-case micro-benchmark? For instance a call to ProxyGetProperty... Last time I measured emitting LFENCE after JSNative calls it was at least a 30% regression. (3) We should use a similar instruction on ARM(64) (I'd also suggest "speculationFence" instead of "flushOutOfOrderBuffer").
(In reply to Jan de Mooij [:jandem] from comment #2) > Sorry for the delay. Some questions: > > (1) I think it's more (or at least as) important to add an LFENCE *after* > the call, since most attacks use an out-of-bounds load and that's easy to do > with DataView.prototype.getInt32 or a similar VMFunction. We would only do > this for VMFunctions that return a value. My understanding, is that the JIT code contains mitigation checking for the type, which is not the case of the C++ code yet. Thus we want to protect the C++ code from an extended speculation window created in the JIT code, thus having an lfence before the call to flush the out-of-order buffer of any pending instruction waiting for the resolution. If the C++ function call returns a Value, then the manipulation of this value should be protected by the JIT value mitigations. If the C++ function call returns a pointer, then this pointer is checked for null-ness before being interpreted as an error/exception. If this branch is trained against such errors, then we might flow in JIT code with a nullptr, which would do a TLB miss, because we always checks the first words below the pointer (for all our HandlePtr types). > (2) How much does this affect a worst-case micro-benchmark? For instance a > call to ProxyGetProperty... Last time I measured emitting LFENCE after > JSNative calls it was at least a 30% regression. I have not checked any micro benchmarks, but this is under the noise level on Octane. > (3) We should use a similar instruction on ARM(64) (I'd also suggest > "speculationFence" instead of "flushOutOfOrderBuffer"). Ok.
(In reply to Nicolas B. Pierron [:nbp] {backlog: ~41} from comment #3) > If the C++ function call returns a Value, then the manipulation of this > value should be protected by the JIT value mitigations. I think the main concern is the VM call reading out-of-bounds (array, strings, typed array, NodeLists...) or something similar. Int32/double unboxing isn't mitigated. > I have not checked any micro benchmarks, but this is under the noise level > on Octane. We should check.. Octane spends a lot of time in JIT code, unlike Speedometer for example.
Delta: - Move speculation barrier after the calls. - Rename the flushOutOfOrderBuffer to speculationBarrier. - Add DOM method calls. - Rename the preference to include all Jit to C++ calls and not only the VMFunctions. TODO: testing the performance impact.
Attachment #8955473 - Flags: review?(jdemooij)
Attachment #8952394 - Attachment is obsolete: true
Attachment #8952394 - Flags: review?(jdemooij)
Comment on attachment 8955473 [details] [diff] [review] Prevent speculative execution after returning from GC-capable C++ code. Review of attachment 8955473 [details] [diff] [review]: ----------------------------------------------------------------- ::: dom/ipc/ContentParent.cpp @@ +2007,5 @@ > for (unsigned int i = 0; i < prefsLen; i++) { > const char* prefName = ContentPrefs::GetEarlyPref(i); > MOZ_ASSERT_IF(i > 0, > + strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0, > + "Content process preferences should be sorted alphabetically."); self-nit: as MOZ_ASSERT_IF with 3 arguments does not exists, I rewrote it to: MOZ_ASSERT(i == 0 || strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0, "Content process preferences should be sorted alphabetically.");
Summary: Spectre mitigation for JIT -> C++ calls: VMFunction wrappers. → Spectre JIT mitigation for speculated data after the execution of C++ calls.
(In reply to Nicolas B. Pierron [:nbp] {backlog: ~38} from comment #5) > Created attachment 8955473 [details] [diff] [review] > Prevent speculative execution after returning from GC-capable C++ code. > and also, Delta: - Add ARM & ARM64 CSDB instructions, based on ARM security update notes. - Move the speculationBarrier function to the generic MacroAssembler. - (Move other spectre function in the checked section of the MacroAssembler)
We also care about LCallNative*, the similar Baseline IC, LGetDOMProperty*, etc right? I think there are other places where we use custom exit frames to call into C++.
Flags: needinfo?(nicolas.b.pierron)
(In reply to Jan de Mooij [:jandem] from comment #9) > We also care about LCallNative*, the similar Baseline IC, LGetDOMProperty*, > etc right? I think there are other places where we use custom exit frames to > call into C++. Right, I have them locally. As far as I know, we do have custom exit frames for ICs, which I have not yet looked at, and for bailouts, which I think we do not care, because it would be harder to inject any random pointers read from it.
Flags: needinfo?(nicolas.b.pierron)
Comment on attachment 8955473 [details] [diff] [review] Prevent speculative execution after returning from GC-capable C++ code. Review of attachment 8955473 [details] [diff] [review]: ----------------------------------------------------------------- Sorry for the delay! This looks good but I'd like to see the CallNative version with the comments below addressed. ::: js/src/jit/CodeGenerator.cpp @@ +4403,5 @@ > masm.loadValue(Address(masm.getStackPointer(), IonDOMMethodExitFrameLayout::offsetOfResult()), > JSReturnOperand); > } > > + // Until C++ code is instrumented against Spectre, prevent speculative I think here we could check if the MIR instruction hasDefUses(), so we don't emit the barrier if we don't use the result? ::: js/src/jit/MacroAssembler.h @@ +1947,5 @@ > > + // ======================================================================== > + // Spectre Mitigations. > + // > + // Spectre attacks are side-channel attacks based on cache polution or Nit: pollution @@ +1953,5 @@ > + // possible: > + // > + // - Stop speculative executions, with memory barriers. Memory barriers > + // forces all branches depending on loads to be resolved, and thus > + // resolve all miss-speculated path. Nit: s/forces/force/, s/path/paths/ @@ +1955,5 @@ > + // - Stop speculative executions, with memory barriers. Memory barriers > + // forces all branches depending on loads to be resolved, and thus > + // resolve all miss-speculated path. > + // > + // - Use conditional move instructions. Some CPU have a branch predictor, Nit: CPUs @@ +1958,5 @@ > + // > + // - Use conditional move instructions. Some CPU have a branch predictor, > + // and not a flag predictor. In such cases, using a conditional move > + // instruction to zero some pointer/index is enough to add a > + // data-dependency which prevent any futher executions until the load is Nit: prevents ::: js/src/jit/x86/Trampoline-x86.cpp @@ +784,5 @@ > } > + > + // Until C++ code is instrumented against Spectre, prevent speculative > + // execution from returning any private data. > + if (f.returnsData() && !JitOptions.spectreJitToCxxCalls) Remove the ! here and everywhere else (does that affect Talos results?).
Attachment #8955473 - Flags: review?(jdemooij)
Comment on attachment 8957529 [details] [diff] [review] Prevent speculative execution after returning from GC-capable C++ code. r=jandem Review of attachment 8957529 [details] [diff] [review]: ----------------------------------------------------------------- Looks good! ::: js/src/jit/CodeGenerator.cpp @@ +4307,5 @@ > > + // Until C++ code is instrumented against Spectre, prevent speculative > + // execution from returning any private data. > + if (JitOptions.spectreJitToCxxCalls && call->mir()->hasLiveDefUses()) > + masm.speculationBarrier(); Also add this here, if (!ignoresReturnValue_): https://searchfox.org/mozilla-central/rev/588d8120aa11738657da93e09a03378bcd1ba8ec/js/src/jit/BaselineIC.cpp#3397 There are also other places where we use custom exit frames like proxy getter calls in Ion CacheIR code. Follow-up bug? @@ +12103,5 @@ > > + // Until C++ code is instrumented against Spectre, prevent speculative > + // execution from returning any private data. > + if (JitOptions.spectreJitToCxxCalls && ins->mir()->hasLiveDefUses()) > + masm.speculationBarrier(); setters don't return a value, so I think this doesn't need mitigations (and there are no hasLiveDefUses) ::: js/xpconnect/src/XPCJSContext.cpp @@ +812,5 @@ > Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.object_mitigations.barriers"); > bool spectreStringMitigations = > Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations"); > bool spectreValueMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.value_masking"); > + bool spectreVMFunctionCalls = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.jit_to_C++_calls"); Nit: spectreJitToCxxCalls for consistency
Attachment #8957529 - Flags: review?(jdemooij) → review+
Attachment #8955473 - Attachment is obsolete: true
Latest performance results: https://treeherder.mozilla.org/perf.html#/compare?originalProject=mozilla-central&newProject=try&newRevision=6fa42b5b7692ce531d3482fee9d073ba08875e4e&framework=1&filter=drom&selectedTimeRange=172800 I still have to fix a few things in the Arm & Arm64 simulators, but this should not block this patch, only the test harness checking with the simulators.
Note for sheriffs & release managers: - This is a known Dromaeo perf regression. - We prefer it landed and toggled off than not landed. ( modules/libpref/init/all.js ) - This introduces new ARM & ARM64 instructions which were only added in January security-update information from ARM. We should watch for new start-up crashes on these platform, in case the security-update information or the implementation of this patch is incorrect.
Pushed by npierron@mozilla.com: https://hg.mozilla.org/integration/mozilla-inbound/rev/90c70e5122f6 Prevent speculative execution after returning from GC-capable C++ code. r=jandem
Pushed by npierron@mozilla.com: https://hg.mozilla.org/integration/mozilla-inbound/rev/bf56e43e67ad Fix arm disassembler by adding support for CSDB instruction. r=me
Status: ASSIGNED → RESOLVED
Closed: 7 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla60
Depends on: 1444856
Depends on: 1459921
Depends on: 1523558
See Also: → 1815170
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: