Bug 1438886 - Prevent speculative execution after returning from GC-capable C++ code. r=jandem

This commit is contained in:
Nicolas B. Pierron 2018-02-20 14:36:11 +00:00
parent b7807456f1
commit 36b48bece7
33 changed files with 216 additions and 47 deletions

View File

@ -2007,8 +2007,8 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
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);
MOZ_ASSERT(i == 0 || strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0,
"Content process preferences should be sorted alphabetically.");
if (!Preferences::MustSendToContentProcesses(prefName)) {
continue;

View File

@ -129,6 +129,7 @@ const char* mozilla::dom::ContentPrefs::gEarlyPrefs[] = {
"javascript.options.parallel_parsing",
"javascript.options.shared_memory",
"javascript.options.spectre.index_masking",
"javascript.options.spectre.jit_to_C++_calls",
"javascript.options.spectre.object_mitigations.barriers",
"javascript.options.spectre.string_mitigations",
"javascript.options.spectre.value_masking",

View File

@ -4305,6 +4305,14 @@ CodeGenerator::visitCallNative(LCallNative* call)
// Load the outparam vp[0] into output register(s).
masm.loadValue(Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()), JSReturnOperand);
// Until C++ code is instrumented against Spectre, prevent speculative
// execution from returning any private data.
if (JitOptions.spectreJitToCxxCalls && !call->mir()->ignoresReturnValue() &&
call->mir()->hasLiveDefUses())
{
masm.speculationBarrier();
}
// The next instruction is removing the footer of the exit frame, so there
// is no need for leaveFakeExitFrame.
@ -4443,6 +4451,11 @@ CodeGenerator::visitCallDOMNative(LCallDOMNative* call)
JSReturnOperand);
}
// Until C++ code is instrumented against Spectre, prevent speculative
// execution from returning any private data.
if (JitOptions.spectreJitToCxxCalls && call->mir()->hasLiveDefUses())
masm.speculationBarrier();
// The next instruction is removing the footer of the exit frame, so there
// is no need for leaveFakeExitFrame.
@ -11993,6 +12006,12 @@ CodeGenerator::visitGetDOMProperty(LGetDOMProperty* ins)
masm.loadValue(Address(masm.getStackPointer(), IonDOMExitFrameLayout::offsetOfResult()),
JSReturnOperand);
}
// Until C++ code is instrumented against Spectre, prevent speculative
// execution from returning any private data.
if (JitOptions.spectreJitToCxxCalls && ins->mir()->hasLiveDefUses())
masm.speculationBarrier();
masm.adjustStack(IonDOMExitFrameLayout::Size());
masm.bind(&haveValue);

View File

@ -238,6 +238,7 @@ DefaultJitOptions::DefaultJitOptions()
SET_DEFAULT(spectreObjectMitigationsBarriers, true);
SET_DEFAULT(spectreStringMitigations, true);
SET_DEFAULT(spectreValueMasking, true);
SET_DEFAULT(spectreJitToCxxCalls, true);
// Toggles whether unboxed plain objects can be created by the VM.
SET_DEFAULT(disableUnboxedObjects, false);

View File

@ -102,6 +102,7 @@ struct DefaultJitOptions
bool spectreObjectMitigationsBarriers;
bool spectreStringMitigations;
bool spectreValueMasking;
bool spectreJitToCxxCalls;
// The options below affect the rest of the VM, and not just the JIT.
bool disableUnboxedObjects;

View File

@ -3656,6 +3656,44 @@ MacroAssembler::emitPreBarrierFastPath(JSRuntime* rt, MIRType type, Register tem
branchTestPtr(Assembler::NonZero, temp2, temp1, noBarrier);
}
// ========================================================================
// Spectre Mitigations.
void
MacroAssembler::spectreMaskIndex(Register index, Register length, Register output)
{
MOZ_ASSERT(JitOptions.spectreIndexMasking);
MOZ_ASSERT(length != output);
MOZ_ASSERT(index != output);
move32(Imm32(0), output);
cmp32Move32(Assembler::Below, index, length, index, output);
}
void
MacroAssembler::spectreMaskIndex(Register index, const Address& length, Register output)
{
MOZ_ASSERT(JitOptions.spectreIndexMasking);
MOZ_ASSERT(index != length.base);
MOZ_ASSERT(length.base != output);
MOZ_ASSERT(index != output);
move32(Imm32(0), output);
cmp32Move32(Assembler::Below, index, length, index, output);
}
void
MacroAssembler::boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure)
{
MOZ_ASSERT(mozilla::IsPowerOfTwo(length));
branch32(Assembler::AboveOrEqual, index, Imm32(length), failure);
// Note: it's fine to clobber the input register, as this is a no-op: it
// only affects speculative execution.
if (JitOptions.spectreIndexMasking)
and32(Imm32(length - 1), index);
}
//}}} check_macroassembler_style
void
@ -3707,41 +3745,6 @@ MacroAssembler::debugAssertObjHasFixedSlots(Register obj, Register scratch)
#endif
}
void
MacroAssembler::spectreMaskIndex(Register index, Register length, Register output)
{
MOZ_ASSERT(JitOptions.spectreIndexMasking);
MOZ_ASSERT(length != output);
MOZ_ASSERT(index != output);
move32(Imm32(0), output);
cmp32Move32(Assembler::Below, index, length, index, output);
}
void
MacroAssembler::spectreMaskIndex(Register index, const Address& length, Register output)
{
MOZ_ASSERT(JitOptions.spectreIndexMasking);
MOZ_ASSERT(index != length.base);
MOZ_ASSERT(length.base != output);
MOZ_ASSERT(index != output);
move32(Imm32(0), output);
cmp32Move32(Assembler::Below, index, length, index, output);
}
void
MacroAssembler::boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure)
{
MOZ_ASSERT(mozilla::IsPowerOfTwo(length));
branch32(Assembler::AboveOrEqual, index, Imm32(length), failure);
// Note: it's fine to clobber the input register, as this is a no-op: it
// only affects speculative execution.
if (JitOptions.spectreIndexMasking)
and32(Imm32(length - 1), index);
}
template <typename T, size_t N, typename P>
static bool
AddPendingReadBarrier(Vector<T*, N, P>& list, T* value)

View File

@ -1957,6 +1957,32 @@ class MacroAssembler : public MacroAssemblerSpecific
Register offsetTemp, Register maskTemp)
DEFINED_ON(mips_shared);
// ========================================================================
// Spectre Mitigations.
//
// Spectre attacks are side-channel attacks based on cache pollution or
// slow-execution of some instructions. We have multiple spectre mitigations
// possible:
//
// - Stop speculative executions, with memory barriers. Memory barriers
// force all branches depending on loads to be resolved, and thus
// resolve all miss-speculated paths.
//
// - Use conditional move instructions. Some CPUs 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 prevents any futher executions until the load is
// resolved.
void spectreMaskIndex(Register index, Register length, Register output);
void spectreMaskIndex(Register index, const Address& length, Register output);
// The length must be a power of two. Performs a bounds check and Spectre index
// masking.
void boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure);
void speculationBarrier() PER_SHARED_ARCH;
//}}} check_macroassembler_decl_style
public:
@ -2141,13 +2167,6 @@ class MacroAssembler : public MacroAssemblerSpecific
store32(Imm32(key.constant()), dest);
}
void spectreMaskIndex(Register index, Register length, Register output);
void spectreMaskIndex(Register index, const Address& length, Register output);
// The length must be a power of two. Performs a bounds check and Spectre index
// masking.
void boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure);
template <typename T>
void guardedCallPreBarrier(const T& address, MIRType type) {
Label done;

View File

@ -149,6 +149,12 @@ struct VMFunction
return returnType;
}
// Whether this function returns anything more than a boolean flag for
// failures.
bool returnsData() const {
return returnType == Type_Pointer || outParam != Type_Void;
}
ArgProperties argProperties(uint32_t explicitArg) const {
return ArgProperties((argumentProperties >> (2 * explicitArg)) & 3);
}

View File

@ -2163,6 +2163,16 @@ Assembler::as_isb_trap()
return writeInst(0xee070f94);
}
BufferOffset
Assembler::as_csdb()
{
// NOP (see as_nop) on architectures where this instruction is not defined.
//
// https://developer.arm.com/-/media/developer/pdf/Cache_Speculation_Side-channels_22Feb18.pdf
// CSDB A32: 1110_0011_0010_0000_1111_0000_0001_0100
return writeInst(0xe320f000 | 0x14);
}
// Control flow stuff:
// bx can *only* branch to a register, never to an immediate.

View File

@ -1606,6 +1606,9 @@ class Assembler : public AssemblerShared
BufferOffset as_dmb_trap();
BufferOffset as_isb_trap();
// Speculation barrier
BufferOffset as_csdb();
// Control flow stuff:
// bx can *only* branch to a register never to an immediate.

View File

@ -5785,6 +5785,17 @@ MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Regist
addDouble(scratchDouble, dest);
}
// ========================================================================
// Spectre Mitigations.
void
MacroAssembler::speculationBarrier()
{
// Spectre mitigation recommended by ARM for cases where csel/cmov cannot be
// used.
as_csdb();
}
//}}} check_macroassembler_style
void

View File

@ -282,6 +282,9 @@ class SimInstruction {
// Test for a nop instruction, which falls under type 1.
inline bool isNopType1() const { return bits(24, 0) == 0x0120F000; }
// Test for a nop instruction, which falls under type 1.
inline bool isCsdbType1() const { return bits(24, 0) == 0x0120F014; }
// Test for a stop instruction.
inline bool isStop() const {
return typeValue() == 7 && bit(24) == 1 && svcValue() >= kStopCode;
@ -3387,6 +3390,8 @@ Simulator::decodeType01(SimInstruction* instr)
}
} else if ((type == 1) && instr->isNopType1()) {
// NOP.
} else if ((type == 1) && instr->isCsdbType1()) {
// Speculation barrier. (No-op for the simulator)
} else {
int rd = instr->rdValue();
int rn = instr->rnValue();

View File

@ -878,6 +878,12 @@ JitRuntime::generateVMWrapper(JSContext* cx, MacroAssembler& masm, const VMFunct
MOZ_ASSERT(f.outParam == Type_Void);
break;
}
// Until C++ code is instrumented against Spectre, prevent speculative
// execution from returning any private data.
if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
masm.speculationBarrier();
masm.leaveExitFrame();
masm.retn(Imm32(sizeof(ExitFrameLayout) +
f.explicitStackSlots() * sizeof(void*) +

View File

@ -1862,6 +1862,16 @@ MacroAssembler::atomicEffectOpJS(Scalar::Type arrayType, const Synchronization&
atomicEffectOp(arrayType, sync, op, value, mem, temp);
}
// ========================================================================
// Spectre Mitigations.
void
MacroAssembler::speculationBarrier()
{
// Conditional speculation barrier.
csdb();
}
//}}} check_macroassembler_style
} // namespace jit

View File

@ -702,6 +702,11 @@ JitRuntime::generateVMWrapper(JSContext* cx, MacroAssembler& masm, const VMFunct
break;
}
// Until C++ code is instrumented against Spectre, prevent speculative
// execution from returning any private data.
if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
masm.speculationBarrier();
masm.leaveExitFrame();
masm.retn(Imm32(sizeof(ExitFrameLayout) +
f.explicitStackSlots() * sizeof(void*) +

View File

@ -1818,6 +1818,13 @@ class Assembler : public MozBaseAssembler {
}
static void nop(Instruction* at);
// Alias for system instructions.
// Conditional speculation barrier.
BufferOffset csdb() {
return hint(CSDB);
}
static void csdb(Instruction* at);
// FP and NEON instructions.
// Move double precision immediate to FP register.
void fmov(const VRegister& vd, double imm);

View File

@ -321,7 +321,10 @@ enum SystemHint {
WFE = 2,
WFI = 3,
SEV = 4,
SEVL = 5
SEVL = 5,
// No-op on architectures where this instruction is not defined.
// https://developer.arm.com/-/media/developer/pdf/Cache_Speculation_Side-channels_22Feb18.pdf
CSDB = 0x14
};
enum BarrierDomain {

View File

@ -228,7 +228,7 @@ void Decoder::DecodeBranchSystemException(const Instruction* instr) {
(instr->Mask(0x003CE000) == 0x00042000) ||
(instr->Mask(0x003FFFC0) == 0x000320C0) ||
(instr->Mask(0x003FF100) == 0x00032100) ||
(instr->Mask(0x003FF200) == 0x00032200) ||
// (instr->Mask(0x003FF200) == 0x00032200) || // match CSDB
(instr->Mask(0x003FF400) == 0x00032400) ||
(instr->Mask(0x003FF800) == 0x00032800) ||
(instr->Mask(0x0038F000) == 0x00005000) ||

View File

@ -1321,6 +1321,11 @@ void Disassembler::VisitSystem(const Instruction* instr) {
form = NULL;
break;
}
case CSDB: {
mnemonic = "csdb";
form = NULL;
break;
}
}
} else if (instr->Mask(MemBarrierFMask) == MemBarrierFixed) {
switch (instr->Mask(MemBarrierMask)) {

View File

@ -309,6 +309,7 @@ class Instruction {
bool IsCBNZ() const;
bool IsLDR() const;
bool IsNOP() const;
bool IsCSDB() const;
bool IsADR() const;
bool IsADRP() const;
bool IsMovz() const;

View File

@ -1123,6 +1123,10 @@ class MacroAssembler : public js::jit::Assembler {
SingleEmissionCheckScope guard(this);
nop();
}
void Csdb() {
SingleEmissionCheckScope guard(this);
csdb();
}
void Rbit(const Register& rd, const Register& rn) {
VIXL_ASSERT(!rd.IsZero());
VIXL_ASSERT(!rn.IsZero());

View File

@ -409,6 +409,11 @@ void Assembler::nop(Instruction* at) {
}
void Assembler::csdb(Instruction* at) {
hint(at, CSDB);
}
BufferOffset Assembler::Logical(const Register& rd, const Register& rn,
const Operand operand, LogicalOp op)
{

View File

@ -85,6 +85,11 @@ bool Instruction::IsNOP() const {
}
bool Instruction::IsCSDB() const {
return Mask(SystemHintMask) == HINT && ImmHint() == CSDB;
}
bool Instruction::IsADR() const {
return Mask(PCRelAddressingMask) == ADR;
}

View File

@ -2326,6 +2326,7 @@ void Simulator::VisitSystem(const Instruction* instr) {
VIXL_ASSERT(instr->Mask(SystemHintMask) == HINT);
switch (instr->ImmHint()) {
case NOP: break;
case CSDB: break;
default: VIXL_UNIMPLEMENTED();
}
} else if (instr->Mask(MemBarrierFMask) == MemBarrierFixed) {

View File

@ -766,6 +766,12 @@ JitRuntime::generateVMWrapper(JSContext* cx, MacroAssembler& masm, const VMFunct
MOZ_ASSERT(f.outParam == Type_Void);
break;
}
// Until C++ code is instrumented against Spectre, prevent speculative
// execution from returning any private data.
if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
masm.speculationBarrier();
masm.leaveExitFrame();
masm.retn(Imm32(sizeof(ExitFrameLayout) +
f.explicitStackSlots() * sizeof(void*) +

View File

@ -3743,9 +3743,13 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_3A, imm, off
m_formatter.immediate16u(imm);
}
void lfence() {
spew("lfence");
m_formatter.twoByteOp(OP_FENCE, (RegisterID)0, 0b101);
}
void mfence() {
spew("mfence");
m_formatter.twoByteOp(OP_FENCE, (RegisterID)0, 6);
m_formatter.twoByteOp(OP_FENCE, (RegisterID)0, 0b110);
}
// Assembler admin methods:

View File

@ -1418,4 +1418,16 @@ MacroAssembler::atomicFetchOpJS(Scalar::Type arrayType, const Synchronization& s
AtomicFetchOpJS(*this, arrayType, sync, op, value, mem, temp1, temp2, output);
}
// ========================================================================
// Spectre Mitigations.
void
MacroAssembler::speculationBarrier()
{
// Spectre mitigation recommended by Intel and AMD suggest to use lfence as
// a way to force all speculative execution of instructions to end.
MOZ_ASSERT(HasSSE2());
masm.lfence();
}
//}}} check_macroassembler_style

View File

@ -782,6 +782,12 @@ JitRuntime::generateVMWrapper(JSContext* cx, MacroAssembler& masm, const VMFunct
MOZ_ASSERT(f.outParam == Type_Void);
break;
}
// Until C++ code is instrumented against Spectre, prevent speculative
// execution from returning any private data.
if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
masm.speculationBarrier();
masm.leaveExitFrame();
masm.retn(Imm32(sizeof(ExitFrameLayout) +
f.explicitStackSlots() * sizeof(void*) +

View File

@ -7287,6 +7287,9 @@ JS_SetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt, uint32_t v
case JSJITCOMPILER_SPECTRE_VALUE_MASKING:
jit::JitOptions.spectreValueMasking = !!value;
break;
case JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS:
jit::JitOptions.spectreJitToCxxCalls = !!value;
break;
case JSJITCOMPILER_ASMJS_ATOMICS_ENABLE:
jit::JitOptions.asmJSAtomicsEnable = !!value;
break;

View File

@ -5894,6 +5894,7 @@ JS_SetOffthreadIonCompilationEnabled(JSContext* cx, bool enabled);
Register(SPECTRE_OBJECT_MITIGATIONS_BARRIERS, "spectre.object-mitigations.barriers") \
Register(SPECTRE_STRING_MITIGATIONS, "spectre.string-mitigations") \
Register(SPECTRE_VALUE_MASKING, "spectre.value-masking") \
Register(SPECTRE_JIT_TO_CXX_CALLS, "spectre.jit-to-C++-calls") \
Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable") \
Register(WASM_FOLD_OFFSETS, "wasm.fold-offsets") \
Register(WASM_DELAY_TIER2, "wasm.delay-tier2")

View File

@ -8601,11 +8601,13 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
jit::JitOptions.spectreObjectMitigationsBarriers = true;
jit::JitOptions.spectreStringMitigations = true;
jit::JitOptions.spectreValueMasking = true;
jit::JitOptions.spectreJitToCxxCalls = true;
} else if (strcmp(str, "off") == 0) {
jit::JitOptions.spectreIndexMasking = false;
jit::JitOptions.spectreObjectMitigationsBarriers = false;
jit::JitOptions.spectreStringMitigations = false;
jit::JitOptions.spectreValueMasking = false;
jit::JitOptions.spectreJitToCxxCalls = false;
} else {
return OptionFailure("spectre-mitigations", str);
}

View File

@ -813,6 +813,7 @@ ReloadPrefsCallback(const char* pref, void* data)
bool spectreStringMitigations =
Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations");
bool spectreValueMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.value_masking");
bool spectreJitToCxxCalls = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.jit_to_C++_calls");
sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
@ -880,6 +881,8 @@ ReloadPrefsCallback(const char* pref, void* data)
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS,
spectreStringMitigations);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING, spectreValueMasking);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS,
spectreJitToCxxCalls);
}
XPCJSContext::~XPCJSContext()

View File

@ -1576,6 +1576,7 @@ pref("javascript.options.spectre.index_masking", true);
pref("javascript.options.spectre.object_mitigations.barriers", true);
pref("javascript.options.spectre.string_mitigations", true);
pref("javascript.options.spectre.value_masking", true);
pref("javascript.options.spectre.jit_to_C++_calls", true);
// Streams API
pref("javascript.options.streams", false);