From 4eac274d32a756fcb26d5c5e6669303ac6b8a98f Mon Sep 17 00:00:00 2001 From: Georgia Kouveli Date: Fri, 24 Jan 2020 16:11:22 +0000 Subject: [PATCH] [arm64] Add support for BTI instruction Bug: v8:10026 Change-Id: I8ee836ee6298415a21cf487bc3d0e5f803fc6186 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1965590 Commit-Queue: Georgia Kouveli Reviewed-by: Ross McIlroy Reviewed-by: Georg Neis Cr-Commit-Position: refs/heads/master@{#66082} --- src/codegen/arm64/assembler-arm64.cc | 24 ++++ src/codegen/arm64/assembler-arm64.h | 5 +- src/codegen/arm64/constants-arm64.h | 31 ++++- src/codegen/arm64/instructions-arm64.h | 18 +++ src/codegen/arm64/macro-assembler-arm64-inl.h | 16 ++- src/codegen/arm64/macro-assembler-arm64.h | 5 +- src/diagnostics/arm64/disasm-arm64.cc | 27 +++- src/execution/arm64/simulator-arm64.cc | 25 ++++ src/execution/arm64/simulator-arm64.h | 124 ++++++++++++++++++ test/cctest/test-assembler-arm64.cc | 70 ++++++++++ test/cctest/test-disasm-arm64.cc | 40 +++++- 11 files changed, 369 insertions(+), 16 deletions(-) diff --git a/src/codegen/arm64/assembler-arm64.cc b/src/codegen/arm64/assembler-arm64.cc index c3bae988f6..1cee01563f 100644 --- a/src/codegen/arm64/assembler-arm64.cc +++ b/src/codegen/arm64/assembler-arm64.cc @@ -1184,6 +1184,30 @@ void Assembler::autia1716() { Emit(AUTIA1716); } void Assembler::paciasp() { Emit(PACIASP); } void Assembler::autiasp() { Emit(AUTIASP); } +void Assembler::bti(BranchTargetIdentifier id) { + SystemHint op; + switch (id) { + case BranchTargetIdentifier::kBti: + op = BTI; + break; + case BranchTargetIdentifier::kBtiCall: + op = BTI_c; + break; + case BranchTargetIdentifier::kBtiJump: + op = BTI_j; + break; + case BranchTargetIdentifier::kBtiJumpCall: + op = BTI_jc; + break; + case BranchTargetIdentifier::kNone: + case BranchTargetIdentifier::kPaciasp: + // We always want to generate a BTI instruction here, so disallow + // skipping its generation or generating a PACIASP instead. + UNREACHABLE(); + } + hint(op); +} + void Assembler::ldp(const CPURegister& rt, const CPURegister& rt2, const MemOperand& src) { LoadStorePair(rt, rt2, src, LoadPairOpFor(rt, rt2)); diff --git a/src/codegen/arm64/assembler-arm64.h b/src/codegen/arm64/assembler-arm64.h index 04fbaf3fb0..664c2bf328 100644 --- a/src/codegen/arm64/assembler-arm64.h +++ b/src/codegen/arm64/assembler-arm64.h @@ -953,7 +953,10 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase { // Conditional speculation barrier. void csdb(); - // Alias for system instructions. + // Branch target identification. + void bti(BranchTargetIdentifier id); + + // No-op. void nop() { hint(NOP); } // Different nop operations are used by the code generator to detect certain diff --git a/src/codegen/arm64/constants-arm64.h b/src/codegen/arm64/constants-arm64.h index ccafae5e14..55cecfc9d5 100644 --- a/src/codegen/arm64/constants-arm64.h +++ b/src/codegen/arm64/constants-arm64.h @@ -389,7 +389,36 @@ enum SystemHint { WFI = 3, SEV = 4, SEVL = 5, - CSDB = 20 + CSDB = 20, + BTI = 32, + BTI_c = 34, + BTI_j = 36, + BTI_jc = 38 +}; + +// In a guarded page, only BTI and PACI[AB]SP instructions are allowed to be +// the target of indirect branches. Details on which kinds of branches each +// instruction allows follow in the comments below: +enum class BranchTargetIdentifier { + // Do not emit a BTI instruction. + kNone, + + // Emit a BTI instruction. Cannot be the target of indirect jumps/calls. + kBti, + + // Emit a "BTI c" instruction. Can be the target of indirect jumps (BR) with + // x16/x17 as the target register, or indirect calls (BLR). + kBtiCall, + + // Emit a "BTI j" instruction. Can be the target of indirect jumps (BR). + kBtiJump, + + // Emit a "BTI jc" instruction, which is a combination of "BTI j" and "BTI c". + kBtiJumpCall, + + // Emit a PACIASP instruction, which acts like a "BTI c" or a "BTI jc", based + // on the value of SCTLR_EL1.BT0. + kPaciasp }; enum BarrierDomain { diff --git a/src/codegen/arm64/instructions-arm64.h b/src/codegen/arm64/instructions-arm64.h index 7fe732e2ba..b4b0febc96 100644 --- a/src/codegen/arm64/instructions-arm64.h +++ b/src/codegen/arm64/instructions-arm64.h @@ -379,6 +379,24 @@ class Instruction { (Mask(MoveWideImmediateMask) == MOVN_w); } + bool IsException() const { return Mask(ExceptionFMask) == ExceptionFixed; } + + bool IsPAuth() const { return Mask(SystemPAuthFMask) == SystemPAuthFixed; } + + bool IsBti() const { + if (Mask(SystemHintFMask) == SystemHintFixed) { + int imm_hint = ImmHint(); + switch (imm_hint) { + case BTI: + case BTI_c: + case BTI_j: + case BTI_jc: + return true; + } + } + return false; + } + bool IsNop(int n) { // A marking nop is an instruction // mov r, r diff --git a/src/codegen/arm64/macro-assembler-arm64-inl.h b/src/codegen/arm64/macro-assembler-arm64-inl.h index aa2df90d0f..0128ec069c 100644 --- a/src/codegen/arm64/macro-assembler-arm64-inl.h +++ b/src/codegen/arm64/macro-assembler-arm64-inl.h @@ -309,9 +309,21 @@ void MacroAssembler::Bfxil(const Register& rd, const Register& rn, unsigned lsb, bfxil(rd, rn, lsb, width); } -void TurboAssembler::Bind(Label* label) { +void TurboAssembler::Bind(Label* label, BranchTargetIdentifier id) { DCHECK(allow_macro_instructions()); - bind(label); + if (id == BranchTargetIdentifier::kNone) { + bind(label); + } else { + // Emit this inside an InstructionAccurateScope to ensure there are no extra + // instructions between the bind and the target identifier instruction. + InstructionAccurateScope scope(this, 1); + bind(label); + if (id == BranchTargetIdentifier::kPaciasp) { + paciasp(); + } else { + bti(id); + } + } } void TurboAssembler::Bl(Label* label) { diff --git a/src/codegen/arm64/macro-assembler-arm64.h b/src/codegen/arm64/macro-assembler-arm64.h index f7fbb1105d..1852831bc2 100644 --- a/src/codegen/arm64/macro-assembler-arm64.h +++ b/src/codegen/arm64/macro-assembler-arm64.h @@ -630,7 +630,8 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase { // Returns false, otherwise. bool TryOneInstrMoveImmediate(const Register& dst, int64_t imm); - inline void Bind(Label* label); + inline void Bind(Label* label, + BranchTargetIdentifier id = BranchTargetIdentifier::kNone); static unsigned CountClearHalfWords(uint64_t imm, unsigned reg_size); @@ -2043,7 +2044,7 @@ class UseScratchRegisterScope { CPURegList list(reg1, reg2); Include(list); } - void Exclude(const Register& reg1, const Register& reg2) { + void Exclude(const Register& reg1, const Register& reg2 = NoReg) { CPURegList list(reg1, reg2); Exclude(list); } diff --git a/src/diagnostics/arm64/disasm-arm64.cc b/src/diagnostics/arm64/disasm-arm64.cc index 5a87f73529..c2a82a5837 100644 --- a/src/diagnostics/arm64/disasm-arm64.cc +++ b/src/diagnostics/arm64/disasm-arm64.cc @@ -1436,7 +1436,7 @@ void DisassemblingDecoder::VisitSystem(Instruction* instr) { #define PAUTH_CASE(NAME, MN) \ case NAME: \ mnemonic = MN; \ - form = NULL; \ + form = nullptr; \ break; PAUTH_SYSTEM_MNEMONICS(PAUTH_CASE) @@ -1478,17 +1478,30 @@ void DisassemblingDecoder::VisitSystem(Instruction* instr) { } } else if (instr->Mask(SystemHintFMask) == SystemHintFixed) { DCHECK(instr->Mask(SystemHintMask) == HINT); + form = nullptr; switch (instr->ImmHint()) { - case NOP: { + case NOP: mnemonic = "nop"; - form = nullptr; break; - } - case CSDB: { + case CSDB: mnemonic = "csdb"; - form = nullptr; break; - } + case BTI: + mnemonic = "bti"; + break; + case BTI_c: + mnemonic = "bti c"; + break; + case BTI_j: + mnemonic = "bti j"; + break; + case BTI_jc: + mnemonic = "bti jc"; + break; + default: + // Fall back to 'hint #'. + form = "'IH"; + mnemonic = "hint"; } } else if (instr->Mask(MemBarrierFMask) == MemBarrierFixed) { switch (instr->Mask(MemBarrierMask)) { diff --git a/src/execution/arm64/simulator-arm64.cc b/src/execution/arm64/simulator-arm64.cc index da770c9f77..1ef9452f5f 100644 --- a/src/execution/arm64/simulator-arm64.cc +++ b/src/execution/arm64/simulator-arm64.cc @@ -298,6 +298,7 @@ void Simulator::SetRedirectInstruction(Instruction* instruction) { Simulator::Simulator(Decoder* decoder, Isolate* isolate, FILE* stream) : decoder_(decoder), + guard_pages_(false), last_debugger_input_(nullptr), log_parameters_(NO_PARAM), isolate_(isolate) { @@ -314,6 +315,7 @@ Simulator::Simulator(Decoder* decoder, Simulator::Simulator() : decoder_(nullptr), + guard_pages_(false), last_debugger_input_(nullptr), log_parameters_(NO_PARAM), isolate_(nullptr) { @@ -361,6 +363,8 @@ void Simulator::ResetState() { // Reset debug helpers. breakpoints_.clear(); break_on_next_ = false; + + btype_ = DefaultBType; } Simulator::~Simulator() { @@ -1494,6 +1498,20 @@ void Simulator::VisitConditionalBranch(Instruction* instr) { } } +Simulator::BType Simulator::GetBTypeFromInstruction( + const Instruction* instr) const { + switch (instr->Mask(UnconditionalBranchToRegisterMask)) { + case BLR: + return BranchAndLink; + case BR: + if (!PcIsInGuardedPage() || (instr->Rn() == 16) || (instr->Rn() == 17)) { + return BranchFromUnguardedOrToIP; + } + return BranchFromGuardedNotToIP; + } + return DefaultBType; +} + void Simulator::VisitUnconditionalBranchToRegister(Instruction* instr) { Instruction* target = reg(instr->Rn()); switch (instr->Mask(UnconditionalBranchToRegisterMask)) { @@ -1513,6 +1531,7 @@ void Simulator::VisitUnconditionalBranchToRegister(Instruction* instr) { default: UNIMPLEMENTED(); } + set_btype(GetBTypeFromInstruction(instr)); } void Simulator::VisitTestBranch(Instruction* instr) { @@ -3096,6 +3115,7 @@ void Simulator::VisitSystem(Instruction* instr) { // range of immediates instead of indicating a different instruction. This // makes the decoding tricky. if (instr->Mask(SystemPAuthFMask) == SystemPAuthFixed) { + // The BType check for PACIASP happens in CheckBType(). switch (instr->Mask(SystemPAuthMask)) { #define DEFINE_PAUTH_FUNCS(SUFFIX, DST, MOD, KEY) \ case PACI##SUFFIX: \ @@ -3145,6 +3165,11 @@ void Simulator::VisitSystem(Instruction* instr) { switch (instr->ImmHint()) { case NOP: case CSDB: + case BTI_jc: + case BTI: + case BTI_c: + case BTI_j: + // The BType checks happen in CheckBType(). break; default: UNIMPLEMENTED(); diff --git a/src/execution/arm64/simulator-arm64.h b/src/execution/arm64/simulator-arm64.h index ae3b6867a9..302f7dfb70 100644 --- a/src/execution/arm64/simulator-arm64.h +++ b/src/execution/arm64/simulator-arm64.h @@ -770,8 +770,125 @@ class Simulator : public DecoderVisitor, public SimulatorBase { virtual void Decode(Instruction* instr) { decoder_->Decode(instr); } + // Branch Target Identification (BTI) + // + // Executing an instruction updates PSTATE.BTYPE, as described in the table + // below. Execution of an instruction on a guarded page is allowed if either: + // * PSTATE.BTYPE is 00, or + // * it is a BTI or PACI[AB]SP instruction that accepts the current value of + // PSTATE.BTYPE (as described in the table below), or + // * it is BRK or HLT instruction that causes some higher-priority exception. + // + // -------------------------------------------------------------------------- + // | Last-executed instruction | Sets | Accepted by | + // | | BTYPE to | BTI | BTI j | BTI c | BTI jc | + // -------------------------------------------------------------------------- + // | - BR from an unguarded page. | | | | | | + // | - BR from guarded page, | | | | | | + // | to x16 or x17. | 01 | | X | X | X | + // -------------------------------------------------------------------------- + // | BR from guarded page, | | | | | | + // | not to x16 or x17. | 11 | | X | | X | + // -------------------------------------------------------------------------- + // | BLR | 10 | | | X | X | + // -------------------------------------------------------------------------- + // | Any other instruction | | | | | | + // |(including RET). | 00 | X | X | X | X | + // -------------------------------------------------------------------------- + // + // PACI[AB]SP is treated either like "BTI c" or "BTI jc", according to the + // value of SCTLR_EL1.BT0. Details available in ARM DDI 0487E.a, D5-2580. + + enum BType { + // Set when executing any instruction, except those cases listed below. + DefaultBType = 0, + + // Set when an indirect branch is taken from an unguarded page, or from a + // guarded page to ip0 or ip1 (x16 or x17), eg "br ip0". + BranchFromUnguardedOrToIP = 1, + + // Set when an indirect branch and link (call) is taken, eg. "blr x0". + BranchAndLink = 2, + + // Set when an indirect branch is taken from a guarded page to a register + // that is not ip0 or ip1 (x16 or x17), eg, "br x0". + BranchFromGuardedNotToIP = 3 + }; + + BType btype() const { return btype_; } + void ResetBType() { btype_ = DefaultBType; } + void set_btype(BType btype) { btype_ = btype; } + + // Helper function to determine BType for branches. + BType GetBTypeFromInstruction(const Instruction* instr) const; + + bool PcIsInGuardedPage() const { return guard_pages_; } + void SetGuardedPages(bool guard_pages) { guard_pages_ = guard_pages; } + + void CheckBTypeForPAuth() { + DCHECK(pc_->IsPAuth()); + Instr instr = pc_->Mask(SystemPAuthMask); + // Only PACI[AB]SP allowed here, but we don't currently support PACIBSP. + CHECK_EQ(instr, PACIASP); + // Check BType allows PACI[AB]SP instructions. + switch (btype()) { + case BranchFromGuardedNotToIP: + // This case depends on the value of SCTLR_EL1.BT0, which we assume + // here to be set. This makes PACI[AB]SP behave like "BTI c", + // disallowing its execution when BTYPE is BranchFromGuardedNotToIP + // (0b11). + FATAL("Executing PACIASP with wrong BType."); + case BranchFromUnguardedOrToIP: + case BranchAndLink: + break; + case DefaultBType: + UNREACHABLE(); + } + } + + void CheckBTypeForBti() { + DCHECK(pc_->IsBti()); + switch (pc_->ImmHint()) { + case BTI_jc: + break; + case BTI: { + DCHECK(btype() != DefaultBType); + FATAL("Executing BTI with wrong BType (expected 0, got %d).", btype()); + break; + } + case BTI_c: + if (btype() == BranchFromGuardedNotToIP) { + FATAL("Executing BTI c with wrong BType (3)."); + } + break; + case BTI_j: + if (btype() == BranchAndLink) { + FATAL("Executing BTI j with wrong BType (2)."); + } + break; + default: + UNIMPLEMENTED(); + } + } + + void CheckBType() { + // On guarded pages, if BType is not zero, take an exception on any + // instruction other than BTI, PACI[AB]SP, HLT or BRK. + if (PcIsInGuardedPage() && (btype() != DefaultBType)) { + if (pc_->IsPAuth()) { + CheckBTypeForPAuth(); + } else if (pc_->IsBti()) { + CheckBTypeForBti(); + } else if (!pc_->IsException()) { + FATAL("Executing non-BTI instruction with wrong BType."); + } + } + } + void ExecuteInstruction() { DCHECK(IsAligned(reinterpret_cast(pc_), kInstrSize)); + CheckBType(); + ResetBType(); CheckBreakNext(); Decode(pc_); increment_pc(); @@ -2192,6 +2309,13 @@ class Simulator : public DecoderVisitor, public SimulatorBase { bool pc_modified_; Instruction* pc_; + // Branch type register, used for branch target identification. + BType btype_; + + // Global flag for enabling guarded pages. + // TODO(arm64): implement guarding at page granularity, rather than globally. + bool guard_pages_; + static const char* xreg_names[]; static const char* wreg_names[]; static const char* sreg_names[]; diff --git a/test/cctest/test-assembler-arm64.cc b/test/cctest/test-assembler-arm64.cc index 8813815317..0665f7a1f7 100644 --- a/test/cctest/test-assembler-arm64.cc +++ b/test/cctest/test-assembler-arm64.cc @@ -1873,6 +1873,76 @@ TEST(branch_to_reg) { CHECK_EQUAL_64(84, x2); } +static void BtiHelper(Register ipreg) { + SETUP(); + + Label jump_target, jump_call_target, call_target, done; + START(); + UseScratchRegisterScope temps(&masm); + temps.Exclude(ipreg); + __ Adr(x0, &jump_target); + __ Br(x0); + __ Nop(); + __ Bind(&jump_target, BranchTargetIdentifier::kBtiJump); + __ Adr(x0, &call_target); + __ Blr(x0); + __ Adr(ipreg, &jump_call_target); + __ Blr(ipreg); + __ Adr(lr, &done); // Make Ret return to done label. + __ Br(ipreg); + __ Bind(&call_target, BranchTargetIdentifier::kBtiCall); + __ Ret(); + __ Bind(&jump_call_target, BranchTargetIdentifier::kBtiJumpCall); + __ Ret(); + __ Bind(&done); + END(); + +#ifdef USE_SIMULATOR + simulator.SetGuardedPages(true); + RUN(); +#endif // USE_SIMULATOR +} + +TEST(bti) { + BtiHelper(x16); + BtiHelper(x17); +} + +TEST(unguarded_bti_is_nop) { + SETUP(); + + Label start, none, c, j, jc; + START(); + __ B(&start); + __ Bind(&none, BranchTargetIdentifier::kBti); + __ Bind(&c, BranchTargetIdentifier::kBtiCall); + __ Bind(&j, BranchTargetIdentifier::kBtiJump); + __ Bind(&jc, BranchTargetIdentifier::kBtiJumpCall); + CHECK(__ SizeOfCodeGeneratedSince(&none) == 4 * kInstrSize); + __ Ret(); + + Label jump_to_c, call_to_j; + __ Bind(&start); + __ Adr(x0, &none); + __ Adr(lr, &jump_to_c); + __ Br(x0); + + __ Bind(&jump_to_c); + __ Adr(x0, &c); + __ Adr(lr, &call_to_j); + __ Br(x0); + + __ Bind(&call_to_j); + __ Adr(x0, &j); + __ Blr(x0); + END(); + +#ifdef USE_SIMULATOR + simulator.SetGuardedPages(false); + RUN(); +#endif // USE_SIMULATOR +} + TEST(compare_branch) { INIT_V8(); SETUP(); diff --git a/test/cctest/test-disasm-arm64.cc b/test/cctest/test-disasm-arm64.cc index 2b46d7ed11..93a0c9a807 100644 --- a/test/cctest/test-disasm-arm64.cc +++ b/test/cctest/test-disasm-arm64.cc @@ -1874,11 +1874,45 @@ TEST_(system_msr) { TEST_(system_nop) { - SET_UP_ASM(); + { + SET_UP_ASM(); + COMPARE(nop(), "nop"); + CLEANUP(); + } + { + SET_UP_MASM(); + COMPARE(Nop(), "nop"); + CLEANUP(); + } +} - COMPARE(nop(), "nop"); +TEST_(bti) { + { + SET_UP_ASM(); - CLEANUP(); + COMPARE(bti(BranchTargetIdentifier::kBti), "bti"); + COMPARE(bti(BranchTargetIdentifier::kBtiCall), "bti c"); + COMPARE(bti(BranchTargetIdentifier::kBtiJump), "bti j"); + COMPARE(bti(BranchTargetIdentifier::kBtiJumpCall), "bti jc"); + COMPARE(hint(BTI), "bti"); + COMPARE(hint(BTI_c), "bti c"); + COMPARE(hint(BTI_j), "bti j"); + COMPARE(hint(BTI_jc), "bti jc"); + + CLEANUP(); + } + + { + SET_UP_MASM(); + + Label dummy1, dummy2, dummy3, dummy4; + COMPARE(Bind(&dummy1, BranchTargetIdentifier::kBti), "bti"); + COMPARE(Bind(&dummy2, BranchTargetIdentifier::kBtiCall), "bti c"); + COMPARE(Bind(&dummy3, BranchTargetIdentifier::kBtiJump), "bti j"); + COMPARE(Bind(&dummy4, BranchTargetIdentifier::kBtiJumpCall), "bti jc"); + + CLEANUP(); + } } TEST(system_pauth) {