// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // The C++ style guide recommends using instead of . However, the // former isn't available in V8. #include // NOLINT(build/c++11) #include "src/api/api-inl.h" #include "src/diagnostics/disassembler.h" #include "src/objects/objects-inl.h" #include "test/cctest/cctest.h" namespace v8 { namespace internal { std::string DisassembleFunction(const char* function) { v8::Local context = CcTest::isolate()->GetCurrentContext(); Handle f = Handle::cast( v8::Utils::OpenHandle(*v8::Local::Cast( CcTest::global()->Get(context, v8_str(function)).ToLocalChecked()))); Address begin = f->code().raw_instruction_start(); Address end = f->code().raw_instruction_end(); Isolate* isolate = CcTest::i_isolate(); std::ostringstream os; Disassembler::Decode(isolate, &os, reinterpret_cast(begin), reinterpret_cast(end), CodeReference(handle(f->code(), isolate))); return os.str(); } struct Matchers { std::string start = "0x[0-9a-f]+ +[0-9a-f]+ +[0-9a-f]+ +"; std::regex map_load_re = std::regex(start + "ldr r([0-9]+), \\[r([0-9]+), #-1\\]"); std::regex load_const_re = std::regex(start + "ldr r([0-9]+), \\[pc, .*"); std::regex cmp_re = std::regex(start + "cmp r([0-9]+), r([0-9]+)"); std::regex bne_re = std::regex(start + "bne (.*)"); std::regex beq_re = std::regex(start + "beq (.*)"); std::regex b_re = std::regex(start + "b (.*)"); std::regex eorne_re = std::regex(start + "eorne r([0-9]+), r([0-9]+), r([0-9]+)"); std::regex eoreq_re = std::regex(start + "eoreq r([0-9]+), r([0-9]+), r([0-9]+)"); std::regex csdb_re = std::regex(start + "csdb"); std::regex load_field_re = std::regex(start + "ldr r([0-9]+), \\[r([0-9]+), #\\+[0-9]+\\]"); std::regex mask_re = std::regex(start + "and r([0-9]+), r([0-9]+), r([0-9]+)"); std::regex untag_re = std::regex(start + "mov r([0-9]+), r([0-9]+), asr #1"); std::string poison_reg = "9"; }; TEST(DisasmPoisonMonomorphicLoad) { #ifdef ENABLE_DISASSEMBLER if (i::FLAG_always_opt || !i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::FLAG_untrusted_code_mitigations = true; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); CompileRun( "function mono(o) { return o.x; };" "%PrepareFunctionForOptimization(mono);" "mono({ x : 1 });" "mono({ x : 1 });" "%OptimizeFunctionOnNextCall(mono);" "mono({ x : 1 });"); Matchers m; std::smatch match; std::string line; std::istringstream reader(DisassembleFunction("mono")); bool poisoning_sequence_found = false; while (std::getline(reader, line)) { if (std::regex_match(line, match, m.map_load_re)) { std::string map_reg = match[1]; std::string object_reg = match[2]; // Matches that the property access sequence is instrumented with // poisoning. We match the following sequence: // // ldr r1, [r0, #-1] ; load map // ldr r2, [pc, #+104] ; load expected map constant // cmp r1, r2 ; compare maps // bne ... ; deopt if different // eorne r9, r9, r9 ; update the poison // csdb ; speculation barrier // ldr r0, [r0, #+11] ; load the field // and r0, r0, r9 ; apply the poison CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.load_const_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.cmp_re)); CHECK_EQ(match[1], map_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.bne_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.eorne_re)); CHECK_EQ(match[1], m.poison_reg); CHECK_EQ(match[2], m.poison_reg); CHECK_EQ(match[3], m.poison_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.csdb_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.load_field_re)); CHECK_EQ(match[2], object_reg); std::string field_reg = match[1]; CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.mask_re)); CHECK_EQ(match[1], field_reg); CHECK_EQ(match[2], field_reg); CHECK_EQ(match[3], m.poison_reg); poisoning_sequence_found = true; break; } } CHECK(poisoning_sequence_found); #endif // ENABLE_DISASSEMBLER } TEST(DisasmPoisonPolymorphicLoad) { #ifdef ENABLE_DISASSEMBLER if (i::FLAG_always_opt || !i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::FLAG_untrusted_code_mitigations = true; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); CompileRun( "function poly(o) { return o.x + 1; };" "let o1 = { x : 1 };" "let o2 = { y : 1 };" "o2.x = 2;" "%PrepareFunctionForOptimization(poly);" "poly(o1);" "poly(o2);" "poly(o1);" "poly(o2);" "%OptimizeFunctionOnNextCall(poly);" "poly(o1);"); Matchers m; std::smatch match; std::string line; std::istringstream reader(DisassembleFunction("poly")); bool poisoning_sequence_found = false; while (std::getline(reader, line)) { if (std::regex_match(line, match, m.map_load_re)) { std::string map_reg = match[1]; std::string object_reg = match[2]; // Matches that the property access sequence is instrumented with // poisoning. We match the following sequence: // // ldr r1, [r0, #-1] ; load map // ldr r2, [pc, #+104] ; load map constant #1 // cmp r1, r2 ; compare maps // beq +Lcase1 ; if match, got to the load // eoreq r9, r9, r9 ; update the poison // csdb ; speculation barrier // ldr r1, [r0, #-1] ; load map // ldr r2, [pc, #+304] ; load map constant #2 // cmp r1, r2 ; compare maps // bne +Ldeopt ; deopt if different // eorne r9, r9, r9 ; update the poison // csdb ; speculation barrier // ldr r0, [r0, #+11] ; load the field // and r0, r0, r9 ; apply the poison // mov r0, r0, asr #1 ; untag // b +Ldone ; goto merge point // Lcase1: // eorne r9, r9, r9 ; update the poison // csdb ; speculation barrier // ldr r0, [r0, #+3] ; load property backing store // and r0, r0, r9 ; apply the poison // ldr r0, [r0, #+3] ; load the property // and r0, r0, r9 ; apply the poison // Ldone: CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.load_const_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.cmp_re)); CHECK_EQ(match[1], map_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.beq_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.eoreq_re)); CHECK_EQ(match[1], m.poison_reg); CHECK_EQ(match[2], m.poison_reg); CHECK_EQ(match[3], m.poison_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.csdb_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.map_load_re)); map_reg = match[1]; CHECK_EQ(match[2], object_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.load_const_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.cmp_re)); CHECK_EQ(match[1], map_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.bne_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.eorne_re)); CHECK_EQ(match[1], m.poison_reg); CHECK_EQ(match[2], m.poison_reg); CHECK_EQ(match[3], m.poison_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.csdb_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.load_field_re)); CHECK_EQ(match[2], object_reg); std::string field_reg = match[1]; CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.mask_re)); CHECK_EQ(match[1], field_reg); CHECK_EQ(match[2], field_reg); CHECK_EQ(match[3], m.poison_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.untag_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.b_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.eorne_re)); CHECK_EQ(match[1], m.poison_reg); CHECK_EQ(match[2], m.poison_reg); CHECK_EQ(match[3], m.poison_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.csdb_re)); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.load_field_re)); CHECK_EQ(match[2], object_reg); std::string storage_reg = match[1]; CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.mask_re)); CHECK_EQ(match[1], storage_reg); CHECK_EQ(match[2], storage_reg); CHECK_EQ(match[3], m.poison_reg); CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.load_field_re)); CHECK_EQ(match[2], storage_reg); field_reg = match[1]; CHECK(std::getline(reader, line)); CHECK(std::regex_match(line, match, m.mask_re)); CHECK_EQ(match[1], field_reg); CHECK_EQ(match[2], field_reg); CHECK_EQ(match[3], m.poison_reg); poisoning_sequence_found = true; break; } } CHECK(poisoning_sequence_found); #endif // ENABLE_DISASSEMBLER } } // namespace internal } // namespace v8