Bug 1433111 - Zero the payload if the Value tag does not match the expected tag. r=jandem

This commit is contained in:
Nicolas B. Pierron 2018-02-02 13:39:24 +00:00
parent 7e7bf4886a
commit f37e8775a7
8 changed files with 122 additions and 13 deletions

View File

@ -311,6 +311,10 @@ CanonicalizeNaN(double d)
* that toString, toObject, toSymbol will return an invalid pointer (because
* some high bits will be set) when called on a Value with a different type
* tag.
*
* - On 32-bit platforms,when unboxing an object/string/symbol Value, we use a
* conditional move (not speculated) to zero the payload register if the type
* doesn't match.
*/
class MOZ_NON_PARAM alignas(8) Value
{

View File

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

View File

@ -94,8 +94,12 @@ struct DefaultJitOptions
mozilla::Maybe<uint32_t> forcedDefaultIonSmallFunctionWarmUpThreshold;
mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator;
// Spectre mitigation flags. Each mitigation has its own flag in order to
// measure the effectiveness of each mitigation with various proof of
// concept.
bool spectreIndexMasking;
bool spectreStringMitigations;
bool spectreValueMasking;
// The options below affect the rest of the VM, and not just the JIT.
bool disableUnboxedObjects;

View File

@ -1343,6 +1343,9 @@ class AssemblerX86Shared : public AssemblerShared
MOZ_CRASH("unexpected operand kind");
}
}
void sbbl(Register src, Register dest) {
masm.sbbl_rr(src.encoding(), dest.encoding());
}
void subl(Register src, Register dest) {
masm.subl_rr(src.encoding(), dest.encoding());
}
@ -1630,6 +1633,9 @@ class AssemblerX86Shared : public AssemblerShared
case Operand::MEM_REG_DISP:
masm.andl_mr(src.disp(), src.base(), dest.encoding());
break;
case Operand::MEM_SCALE:
masm.andl_mr(src.disp(), src.base(), src.index(), src.scale(), dest.encoding());
break;
default:
MOZ_CRASH("unexpected operand kind");
}
@ -2114,6 +2120,9 @@ class AssemblerX86Shared : public AssemblerShared
case Operand::MEM_REG_DISP:
masm.push_m(src.disp(), src.base());
break;
case Operand::MEM_SCALE:
masm.push_m(src.disp(), src.base(), src.index(), src.scale());
break;
default:
MOZ_CRASH("unexpected operand kind");
}

View File

@ -303,6 +303,11 @@ public:
spew("push " MEM_ob, ADDR_ob(offset, base));
m_formatter.oneByteOp(OP_GROUP5_Ev, offset, base, GROUP5_OP_PUSH);
}
void push_m(int32_t offset, RegisterID base, RegisterID index, int scale)
{
spew("push " MEM_obs, ADDR_obs(offset, base, index, scale));
m_formatter.oneByteOp(OP_GROUP5_Ev, offset, base, index, scale, GROUP5_OP_PUSH);
}
void pop_m(int32_t offset, RegisterID base)
{
@ -923,6 +928,12 @@ public:
m_formatter.oneByteOp(OP_AND_GvEv, offset, base, dst);
}
void andl_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
{
spew("andl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst));
m_formatter.oneByteOp(OP_AND_GvEv, offset, base, index, scale, dst);
}
void andl_rm(RegisterID src, int32_t offset, RegisterID base)
{
spew("andl %s, " MEM_ob, GPReg32Name(src), ADDR_ob(offset, base));
@ -1232,6 +1243,12 @@ public:
}
}
void sbbl_rr(RegisterID src, RegisterID dst)
{
spew("sbbl %s, %s", GPReg32Name(src), GPReg32Name(dst));
m_formatter.oneByteOp(OP_SBB_GvEv, src, dst);
}
void subl_rr(RegisterID src, RegisterID dst)
{
spew("subl %s, %s", GPReg32Name(src), GPReg32Name(dst));

View File

@ -121,20 +121,28 @@ CodeGeneratorX86::visitUnbox(LUnbox* unbox)
{
// Note that for unbox, the type and payload indexes are switched on the
// inputs.
Operand type = ToOperand(unbox->type());
Operand payload = ToOperand(unbox->payload());
Register output = ToRegister(unbox->output());
MUnbox* mir = unbox->mir();
JSValueTag tag = MIRTypeToTag(mir->type());
if (mir->fallible()) {
masm.cmp32(ToOperand(unbox->type()), Imm32(tag));
masm.cmp32(type, Imm32(tag));
bailoutIf(Assembler::NotEqual, unbox->snapshot());
} else {
#ifdef DEBUG
Label ok;
masm.branch32(Assembler::Equal, ToOperand(unbox->type()), Imm32(tag), &ok);
masm.branch32(Assembler::Equal, type, Imm32(tag), &ok);
masm.assumeUnreachable("Infallible unbox type mismatch");
masm.bind(&ok);
#endif
}
// Note: If spectreValueMasking is disabled, then this instruction will
// default to a no-op as long as the lowering allocate the same register for
// the output and the payload.
masm.unboxNonDouble(type, payload, output, ValueTypeFromMIRType(mir->type()));
}
void

View File

@ -119,8 +119,16 @@ LIRGeneratorX86::visitUnbox(MUnbox* unbox)
// Swap the order we use the box pieces so we can re-use the payload register.
LUnbox* lir = new(alloc()) LUnbox;
lir->setOperand(0, usePayloadInRegisterAtStart(inner));
lir->setOperand(1, useType(inner, LUse::ANY));
bool reusePayloadReg = !JitOptions.spectreValueMasking ||
unbox->type() == MIRType::Int32 ||
unbox->type() == MIRType::Boolean;
if (reusePayloadReg) {
lir->setOperand(0, usePayloadInRegisterAtStart(inner));
lir->setOperand(1, useType(inner, LUse::ANY));
} else {
lir->setOperand(0, usePayload(inner, LUse::REGISTER));
lir->setOperand(1, useType(inner, LUse::ANY));
}
if (unbox->fallible())
assignSnapshot(lir, unbox->bailoutKind());
@ -130,7 +138,10 @@ LIRGeneratorX86::visitUnbox(MUnbox* unbox)
// recoverable. Unbox's purpose is to eagerly kill the definition of a type
// tag, so keeping both alive (for the purpose of gcmaps) is unappealing.
// Instead, we create a new virtual register.
defineReuseInput(lir, unbox, 0);
if (reusePayloadReg)
defineReuseInput(lir, unbox, 0);
else
define(lir, unbox);
}
void

View File

@ -680,15 +680,67 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared
movl(ImmType(type), dest.typeReg());
}
void unboxNonDouble(const ValueOperand& src, Register dest, JSValueType type) {
if (src.payloadReg() != dest)
movl(src.payloadReg(), dest);
void unboxNonDouble(const ValueOperand& src, Register dest, JSValueType type, Register scratch = InvalidReg) {
unboxNonDouble(Operand(src.typeReg()), Operand(src.payloadReg()), dest, type, scratch);
}
void unboxNonDouble(const Operand& tag, const Operand& payload, Register dest, JSValueType type, Register scratch = InvalidReg) {
auto movPayloadToDest = [&]() {
if (payload.kind() != Operand::REG || !payload.containsReg(dest))
movl(payload, dest);
};
if (!JitOptions.spectreValueMasking) {
movPayloadToDest();
return;
}
// Spectre mitigation: We zero the payload if the tag does not match the
// expected type and if this is a pointer type.
if (type == JSVAL_TYPE_INT32 || type == JSVAL_TYPE_BOOLEAN) {
movPayloadToDest();
return;
}
if (!tag.containsReg(dest) && !payload.containsReg(dest)) {
// We zero the destination register and move the payload into it if
// the tag corresponds to the given type.
xorl(dest, dest);
cmpl(Imm32(JSVAL_TYPE_TO_TAG(type)), tag);
cmovCCl(Condition::Equal, payload, dest);
return;
}
if (scratch == InvalidReg || scratch == dest ||
tag.containsReg(scratch) || payload.containsReg(scratch))
{
// UnboxedLayout::makeConstructorCode calls extractObject with a
// scratch register which aliases the tag register, thus we cannot
// assert the above condition.
scratch = InvalidReg;
}
// The destination register aliases one of the operands. We create a
// zero value either in a scratch register or on the stack and use it
// to reset the destination register after reading both the tag and the
// payload.
Operand zero(Address(esp, 0));
if (scratch == InvalidReg) {
push(Imm32(0));
} else {
xorl(scratch, scratch);
zero = Operand(scratch);
}
cmpl(Imm32(JSVAL_TYPE_TO_TAG(type)), tag);
movPayloadToDest();
cmovCCl(Condition::NotEqual, zero, dest);
if (scratch == InvalidReg) {
addl(Imm32(sizeof(void*)), esp);
}
}
void unboxNonDouble(const Address& src, Register dest, JSValueType type) {
movl(payloadOf(src), dest);
unboxNonDouble(tagOf(src), payloadOf(src), dest, type);
}
void unboxNonDouble(const BaseIndex& src, Register dest, JSValueType type) {
movl(payloadOf(src), dest);
unboxNonDouble(tagOf(src), payloadOf(src), dest, type);
}
void unboxInt32(const ValueOperand& src, Register dest) {
unboxNonDouble(src, dest, JSVAL_TYPE_INT32);
@ -771,17 +823,20 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared
// Extended unboxing API. If the payload is already in a register, returns
// that register. Otherwise, provides a move to the given scratch register,
// and returns that.
Register extractObject(const Address& address, Register scratch) {
movl(payloadOf(address), scratch);
return scratch;
Register extractObject(const Address& address, Register dest) {
unboxObject(address, dest);
return dest;
}
Register extractObject(const ValueOperand& value, Register scratch) {
unboxNonDouble(value, value.payloadReg(), JSVAL_TYPE_OBJECT, scratch);
return value.payloadReg();
}
Register extractString(const ValueOperand& value, Register scratch) {
unboxNonDouble(value, value.payloadReg(), JSVAL_TYPE_STRING, scratch);
return value.payloadReg();
}
Register extractSymbol(const ValueOperand& value, Register scratch) {
unboxNonDouble(value, value.payloadReg(), JSVAL_TYPE_SYMBOL, scratch);
return value.payloadReg();
}
Register extractInt32(const ValueOperand& value, Register scratch) {