//===- AArch64MCLFIRewriter.cpp ---------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements the AArch64MCLFIRewriter class, the AArch64 specific // subclass of MCLFIRewriter. // //===----------------------------------------------------------------------===// #include "AArch64MCLFIRewriter.h" #include "AArch64AddressingModes.h" #include "MCTargetDesc/AArch64MCTargetDesc.h" #include "Utils/AArch64BaseInfo.h" #include "llvm/ADT/Twine.h" #include "llvm/MC/MCInst.h" #include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSubtargetInfo.h" using namespace llvm; // LFI reserved registers. static constexpr MCRegister LFIBaseReg = AArch64::X27; static constexpr MCRegister LFIAddrReg = AArch64::X28; static constexpr MCRegister LFIScratchReg = AArch64::X26; static constexpr MCRegister LFICtxReg = AArch64::X25; // Offset into the context register block (pointed to by LFICtxReg) where the // thread pointer is stored. This is a scaled offset (multiplied by 8 for // 64-bit loads), so a value of 2 means an actual byte offset of 16. static constexpr unsigned LFITPOffset = 2; // Byte offset from the sandbox base register where the syscall handler address // is stored (negative because it is below the sandbox base). static constexpr int LFISyscallOffset = -8; static bool isSyscall(const MCInst &Inst) { return Inst.getOpcode() == AArch64::SVC; } static bool isPrivilegedTP(int64_t Reg) { return Reg == AArch64SysReg::TPIDR_EL1 || Reg == AArch64SysReg::TPIDR_EL2 || Reg == AArch64SysReg::TPIDR_EL3; } static bool isTPRead(const MCInst &Inst) { return Inst.getOpcode() == AArch64::MRS && Inst.getOperand(1).getImm() == AArch64SysReg::TPIDR_EL0; } static bool isTPWrite(const MCInst &Inst) { return Inst.getOpcode() == AArch64::MSR && Inst.getOperand(0).getImm() == AArch64SysReg::TPIDR_EL0; } static bool isPrivilegedTPAccess(const MCInst &Inst) { if (Inst.getOpcode() == AArch64::MRS) return isPrivilegedTP(Inst.getOperand(1).getImm()); if (Inst.getOpcode() == AArch64::MSR) return isPrivilegedTP(Inst.getOperand(0).getImm()); return false; } MCRegister AArch64MCLFIRewriter::mayModifyReserved(const MCInst &Inst) const { for (MCRegister Reg : {LFIAddrReg, LFIBaseReg, LFICtxReg}) { if (mayModifyRegister(Inst, Reg)) return Reg; } return {}; } void AArch64MCLFIRewriter::emitInst(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { Out.emitInstruction(Inst, STI); } void AArch64MCLFIRewriter::emitAddMask(MCRegister Dest, MCRegister Src, MCStreamer &Out, const MCSubtargetInfo &STI) { // add Dest, LFIBaseReg, W(Src), uxtw MCInst Inst; Inst.setOpcode(AArch64::ADDXrx); Inst.addOperand(MCOperand::createReg(Dest)); Inst.addOperand(MCOperand::createReg(LFIBaseReg)); Inst.addOperand(MCOperand::createReg(getWRegFromXReg(Src))); Inst.addOperand( MCOperand::createImm(AArch64_AM::getArithExtendImm(AArch64_AM::UXTW, 0))); emitInst(Inst, Out, STI); } void AArch64MCLFIRewriter::emitBranch(unsigned Opcode, MCRegister Target, MCStreamer &Out, const MCSubtargetInfo &STI) { MCInst Branch; Branch.setOpcode(Opcode); Branch.addOperand(MCOperand::createReg(Target)); emitInst(Branch, Out, STI); } void AArch64MCLFIRewriter::emitPendingTLSDescCall(MCStreamer &Out, const MCSubtargetInfo &STI) { if (!PendingTLSDescCall) return; MCInst Marker; Marker.setOpcode(AArch64::TLSDESCCALL); Marker.addOperand(MCOperand::createExpr(PendingTLSDescCall)); PendingTLSDescCall = nullptr; emitInst(Marker, Out, STI); } void AArch64MCLFIRewriter::emitMov(MCRegister Dest, MCRegister Src, MCStreamer &Out, const MCSubtargetInfo &STI) { // orr Dest, xzr, Src MCInst Inst; Inst.setOpcode(AArch64::ORRXrs); Inst.addOperand(MCOperand::createReg(Dest)); Inst.addOperand(MCOperand::createReg(AArch64::XZR)); Inst.addOperand(MCOperand::createReg(Src)); Inst.addOperand(MCOperand::createImm(0)); emitInst(Inst, Out, STI); } // {br,blr} xN // -> // add x28, x27, wN, uxtw // {br,blr} x28 void AArch64MCLFIRewriter::rewriteIndirectBranch(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { assert(Inst.getNumOperands() >= 1 && Inst.getOperand(0).isReg() && "expected register operand"); MCRegister BranchReg = Inst.getOperand(0).getReg(); // Guard the branch target through X28. emitAddMask(LFIAddrReg, BranchReg, Out, STI); emitPendingTLSDescCall(Out, STI); emitBranch(Inst.getOpcode(), LFIAddrReg, Out, STI); } // ret xN (where xN != x30) // -> // add x28, x27, wN, uxtw // ret x28 // // ret (x30) is safe since x30 is always within the sandbox. void AArch64MCLFIRewriter::rewriteReturn(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { assert(Inst.getNumOperands() >= 1 && Inst.getOperand(0).isReg() && "expected register operand"); // RET through LR is safe since LR is always within sandbox. if (Inst.getOperand(0).getReg() != AArch64::LR) rewriteIndirectBranch(Inst, Out, STI); else emitInst(Inst, Out, STI); } // modify x30 // -> // modify x30 // add x30, x27, w30, uxtw void AArch64MCLFIRewriter::rewriteLRModification(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { emitInst(Inst, Out, STI); emitAddMask(AArch64::LR, AArch64::LR, Out, STI); } // svc #0 // -> // mov x26, x30 // ldur x30, [x27, #-8] // blr x30 // add x30, x27, w26, uxtw void AArch64MCLFIRewriter::rewriteSyscall(const MCInst &, MCStreamer &Out, const MCSubtargetInfo &STI) { // Save LR to scratch. emitMov(LFIScratchReg, AArch64::LR, Out, STI); // Load syscall handler address from negative offset from sandbox base. MCInst Load; Load.setOpcode(AArch64::LDURXi); Load.addOperand(MCOperand::createReg(AArch64::LR)); Load.addOperand(MCOperand::createReg(LFIBaseReg)); Load.addOperand(MCOperand::createImm(LFISyscallOffset)); emitInst(Load, Out, STI); // Call the runtime. emitBranch(AArch64::BLR, AArch64::LR, Out, STI); // Restore LR with guard. emitAddMask(AArch64::LR, LFIScratchReg, Out, STI); } // mrs xN, tpidr_el0 // -> // ldr xN, [x25, #16] void AArch64MCLFIRewriter::rewriteTPRead(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { MCRegister DestReg = Inst.getOperand(0).getReg(); MCInst Load; Load.setOpcode(AArch64::LDRXui); Load.addOperand(MCOperand::createReg(DestReg)); Load.addOperand(MCOperand::createReg(LFICtxReg)); Load.addOperand(MCOperand::createImm(LFITPOffset)); emitInst(Load, Out, STI); } // msr tpidr_el0, xN // -> // str xN, [x25, #16] void AArch64MCLFIRewriter::rewriteTPWrite(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { MCRegister SrcReg = Inst.getOperand(1).getReg(); MCInst Store; Store.setOpcode(AArch64::STRXui); Store.addOperand(MCOperand::createReg(SrcReg)); Store.addOperand(MCOperand::createReg(LFICtxReg)); Store.addOperand(MCOperand::createImm(LFITPOffset)); emitInst(Store, Out, STI); } // NOTE: when adding new rewrites, the size estimates in // AArch64InstrInfo::getLFIInstSizeInBytes must be updated to match. void AArch64MCLFIRewriter::doRewriteInst(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { if (Inst.getOpcode() == AArch64::TLSDESCCALL) { PendingTLSDescCall = Inst.getOperand(0).getExpr(); return; } // Reserved register modification is an error. if (MCRegister Reg = mayModifyReserved(Inst)) { error(Inst, Twine("illegal modification of reserved LFI register ") + RegInfo->getName(Reg)); return; } // System instructions. if (isSyscall(Inst)) return rewriteSyscall(Inst, Out, STI); if (isTPRead(Inst)) return rewriteTPRead(Inst, Out, STI); if (isTPWrite(Inst)) return rewriteTPWrite(Inst, Out, STI); if (isPrivilegedTPAccess(Inst)) { error(Inst, "illegal access to privileged thread pointer register"); return; } // Control flow. switch (Inst.getOpcode()) { case AArch64::RET: return rewriteReturn(Inst, Out, STI); case AArch64::BR: case AArch64::BLR: return rewriteIndirectBranch(Inst, Out, STI); } // Link register modification. if (explicitlyModifiesRegister(Inst, AArch64::LR)) return rewriteLRModification(Inst, Out, STI); emitInst(Inst, Out, STI); } bool AArch64MCLFIRewriter::rewriteInst(const MCInst &Inst, MCStreamer &Out, const MCSubtargetInfo &STI) { // The guard prevents rewrite-recursion when we emit instructions from inside // the rewriter (such instructions should not be rewritten). if (!Enabled || Guard) return false; Guard = true; doRewriteInst(Inst, Out, STI); Guard = false; return true; }