CVE-2019-5755 分析

一、前言

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

  • 复现用的 v8 版本为 7.1.302.28 (或者commit ID a62e9dd69957d9b1d0a56f825506408960a283fc 前的版本也可)

二、环境搭建

  • 切换 v8 版本,然后编译:

    1
    2
    3
    4
    git checkout 7.1.302.28
    gclient sync
    tools/dev/v8gen.py x64.debug
    ninja -C out.gn/x64.debug
  • 启动 turbolizer。如果原先版本的 turbolizer 无法使用,则可以使用在线版本的 turbolizer

    1
    2
    3
    4
    5
    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 函数(对照函数)的源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    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 函数调用来生成的,其目的是用于判断左右操作数是否相同。

    具体来说是通过以下调用链生成:

    1
    2
    3
    void InliningPhase::Run(...)
    Reduction JSCallReducer::ReduceJSCall(...)
    Reduction JSCallReducer::ReduceObjectIs(Node* node)

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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 结点的类型,它将沿着以下调用链

    1
    2
    3
    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函数并计算其类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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 结点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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 的详细执行过程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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 结点的生成方式。这两种结点的生成都将通过以下调用链:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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 中获取操作数的相关信息。操作数信息一共有以下五种类型:

    1
    2
    3
    4
    5
    6
    7
    8
    // 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):$(-\infty, -2^{31}) \cup [2^{32}, \infty)$
      • 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})$
      1
      2
      3
        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 如下

1
2
3
4
5
6
7
8
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(咋一看还没认出来):

1
2
3
4
5
6
7
8
9
10
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 修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
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。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 函数调用:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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 结点(原先的代码只是获取索引值):

1
2
3
4
5
6
7
8
9
10
11
12
13
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 编写就没办法继续下去,只能就此终止。

五、后记

  • 该漏洞补丁的详细信息请查阅此处

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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());
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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。

六、参考

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2021 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~