| //===- X86MCLFIRewriter.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 X86MCLFIRewriter class, which rewrites X86-64 |
| // instructions for LFI (Lightweight Fault Isolation) sandboxing. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "X86MCLFIRewriter.h" |
| #include "X86BaseInfo.h" |
| #include "X86MCTargetDesc.h" |
| #include "llvm/MC/MCContext.h" |
| #include "llvm/MC/MCExpr.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 = X86::R14; |
| static constexpr MCRegister LFIScratchReg = X86::R11; |
| static constexpr MCRegister LFITPReg = X86::R15; |
| |
| // Byte offset into the context register file (pointed to by R15) where the |
| // thread pointer is stored. |
| static constexpr int TPOffset = 16; |
| |
| static bool isSyscall(const MCInst &Inst) { |
| return Inst.getOpcode() == X86::SYSCALL; |
| } |
| |
| // Find the index of the memory operand if it has an %fs segment override. |
| // Returns -1 if there is no memory operand or no %fs override. |
| static int findFSMemOperand(const MCInst &Inst, const MCInstrInfo &InstInfo) { |
| const MCInstrDesc &Desc = InstInfo.get(Inst.getOpcode()); |
| int MemRefIdx = X86II::getMemoryOperandNo(Desc.TSFlags); |
| if (MemRefIdx < 0) |
| return -1; |
| int MemIdx = MemRefIdx + X86II::getOperandBias(Desc); |
| const MCOperand &Seg = Inst.getOperand(MemIdx + X86::AddrSegmentReg); |
| if (Seg.isReg() && Seg.getReg() == X86::FS) |
| return MemIdx; |
| return -1; |
| } |
| |
| // Return true if the instruction reads from Reg. |
| static bool readsRegister(const MCInst &Inst, const MCInstrDesc &Desc, |
| MCRegister Reg, const MCRegisterInfo &RI) { |
| for (unsigned I = Desc.getNumDefs(), E = Inst.getNumOperands(); I < E; ++I) { |
| const MCOperand &Op = Inst.getOperand(I); |
| if (Op.isReg() && Op.getReg() && RI.regsOverlap(Op.getReg(), Reg)) |
| return true; |
| } |
| for (MCPhysReg Use : Desc.implicit_uses()) |
| if (RI.regsOverlap(Use, Reg)) |
| return true; |
| return false; |
| } |
| |
| // Return true if Reg is absent or a 64-bit general-purpose register. |
| static bool isGR64OrNone(MCRegister Reg) { |
| return Reg == X86::NoRegister || |
| getX86MCRegisterClass(X86::GR64RegClassID).contains(Reg); |
| } |
| |
| // syscall |
| // -> |
| // leaq .Ltmp(%rip), %r11 |
| // jmpq *(%r14) |
| // .Ltmp: |
| void X86::X86MCLFIRewriter::rewriteSyscall(const MCInst &Inst, MCStreamer &Out, |
| const MCSubtargetInfo &STI) { |
| MCSymbol *Symbol = Out.getContext().createTempSymbol(); |
| |
| // leaq .Ltmp(%rip), %r11 |
| MCInst Lea; |
| Lea.setOpcode(X86::LEA64r); |
| Lea.addOperand(MCOperand::createReg(LFIScratchReg)); |
| Lea.addOperand(MCOperand::createReg(X86::RIP)); |
| Lea.addOperand(MCOperand::createImm(1)); |
| Lea.addOperand(MCOperand::createReg(X86::NoRegister)); |
| Lea.addOperand( |
| MCOperand::createExpr(MCSymbolRefExpr::create(Symbol, Out.getContext()))); |
| Lea.addOperand(MCOperand::createReg(X86::NoRegister)); |
| Out.emitInstruction(Lea, STI); |
| |
| // jmpq *(%r14) |
| MCInst Jmp; |
| Jmp.setOpcode(X86::JMP64m); |
| Jmp.addOperand(MCOperand::createReg(LFIBaseReg)); |
| Jmp.addOperand(MCOperand::createImm(1)); |
| Jmp.addOperand(MCOperand::createReg(X86::NoRegister)); |
| Jmp.addOperand(MCOperand::createImm(-8)); |
| Jmp.addOperand(MCOperand::createReg(X86::NoRegister)); |
| Out.emitInstruction(Jmp, STI); |
| |
| Out.emitLabel(Symbol); |
| } |
| |
| // Emit: movq TPOffset(%r15), %Reg |
| static void emitTPLoad(MCRegister Reg, MCStreamer &Out, |
| const MCSubtargetInfo &STI) { |
| MCInst Mov; |
| Mov.setOpcode(X86::MOV64rm); |
| Mov.addOperand(MCOperand::createReg(Reg)); |
| Mov.addOperand(MCOperand::createReg(LFITPReg)); |
| Mov.addOperand(MCOperand::createImm(1)); |
| Mov.addOperand(MCOperand::createReg(X86::NoRegister)); |
| Mov.addOperand(MCOperand::createImm(TPOffset)); |
| Mov.addOperand(MCOperand::createReg(X86::NoRegister)); |
| Out.emitInstruction(Mov, STI); |
| } |
| |
| bool X86::X86MCLFIRewriter::isFSAccess(const MCInst &Inst) { |
| return (mayLoad(Inst) || mayStore(Inst)) && |
| findFSMemOperand(Inst, *InstInfo) >= 0; |
| } |
| |
| // Rewrite %fs-segment memory accesses to use the virtual thread pointer stored |
| // at TPOffset(%r15). The actual memory access is currently unsandboxed because |
| // load/store sandboxing is not yet supported. Example rewrites: |
| // |
| // movq %fs:0, %rax |
| // -> |
| // movq 16(%r15), %rax |
| // |
| // movq %fs:(%rdi), %rax |
| // -> |
| // movq 16(%r15), %rax |
| // movq (%rax, %rdi), %rax |
| // |
| // movq %fs:8(%rdi, %rsi, 2), %rax |
| // -> |
| // movq 16(%r15), %rax |
| // leaq (%rax, %rdi), %rax |
| // movq 8(%rax, %rsi, 2), %rax |
| void X86::X86MCLFIRewriter::rewriteFSAccess(const MCInst &Inst, MCStreamer &Out, |
| const MCSubtargetInfo &STI) { |
| int MemIdx = findFSMemOperand(Inst, *InstInfo); |
| assert(MemIdx >= 0); |
| |
| MCRegister BaseReg = Inst.getOperand(MemIdx + X86::AddrBaseReg).getReg(); |
| MCRegister IndexReg = Inst.getOperand(MemIdx + X86::AddrIndexReg).getReg(); |
| bool HasBase = BaseReg != X86::NoRegister; |
| bool HasIndex = IndexReg != X86::NoRegister; |
| bool HasDisp = !Inst.getOperand(MemIdx + X86::AddrDisp).isImm() || |
| Inst.getOperand(MemIdx + X86::AddrDisp).getImm() != 0; |
| |
| // %fs:0 -> TPOffset(%r15) |
| if (!HasBase && !HasIndex && !HasDisp) { |
| MCInst Modified(Inst); |
| Modified.getOperand(MemIdx + X86::AddrBaseReg).setReg(LFITPReg); |
| Modified.getOperand(MemIdx + X86::AddrDisp).setImm(TPOffset); |
| Modified.getOperand(MemIdx + X86::AddrSegmentReg).setReg(X86::NoRegister); |
| return Out.emitInstruction(Modified, STI); |
| } |
| |
| if (!isGR64OrNone(BaseReg) || !isGR64OrNone(IndexReg) || |
| BaseReg == X86::RSP || BaseReg == X86::RIP) |
| return error(Inst, "unsupported addressing mode for %fs access"); |
| |
| const MCInstrDesc &Desc = InstInfo->get(Inst.getOpcode()); |
| |
| // Reuse operand 0 as the TP temporary when the instruction writes it without |
| // also reading it, otherwise use %r11. |
| MCRegister TPDest = LFIScratchReg; |
| if (MemIdx > 0 && Inst.getOperand(0).isReg()) { |
| MCRegister DestReg = Inst.getOperand(0).getReg(); |
| if (Desc.getNumDefs() > 0 && |
| getX86MCRegisterClass(X86::GR64RegClassID).contains(DestReg) && |
| !readsRegister(Inst, Desc, DestReg, *RegInfo)) |
| TPDest = DestReg; |
| } |
| |
| if (TPDest == LFIScratchReg && |
| readsRegister(Inst, Desc, LFIScratchReg, *RegInfo)) |
| return error(Inst, "%fs access reads reserved register %r11"); |
| |
| emitTPLoad(TPDest, Out, STI); |
| |
| // Both slots occupied: the compute base via lea. For example: |
| // |
| // movq %fs:8(%rdi,%rsi,2), %rax |
| // -> |
| // movq 16(%r15), %rax |
| // leaq (%rax,%rdi), %rax |
| // movq 8(%rax,%rsi,2), %rax |
| if (HasBase && HasIndex) { |
| MCInst Lea; |
| Lea.setOpcode(X86::LEA64r); |
| Lea.addOperand(MCOperand::createReg(TPDest)); |
| Lea.addOperand(MCOperand::createReg(TPDest)); |
| Lea.addOperand(MCOperand::createImm(1)); |
| Lea.addOperand(MCOperand::createReg(BaseReg)); |
| Lea.addOperand(MCOperand::createImm(0)); |
| Lea.addOperand(MCOperand::createReg(X86::NoRegister)); |
| Out.emitInstruction(Lea, STI); |
| } |
| |
| // Emit the access with TPDest as the new base, and the original base |
| // (offset from %fs) as the new index. For example: |
| // |
| // movq %fs:(%rdi), %rax |
| // -> |
| // movq 16(%r15), %rax |
| // movq (%rax,%rdi), %rax |
| MCInst Modified(Inst); |
| Modified.getOperand(MemIdx + X86::AddrBaseReg).setReg(TPDest); |
| if (HasBase && !HasIndex) |
| Modified.getOperand(MemIdx + X86::AddrIndexReg).setReg(BaseReg); |
| Modified.getOperand(MemIdx + X86::AddrSegmentReg).setReg(X86::NoRegister); |
| Out.emitInstruction(Modified, STI); |
| } |
| |
| void X86::X86MCLFIRewriter::doRewriteInst(const MCInst &Inst, MCStreamer &Out, |
| const MCSubtargetInfo &STI) { |
| if (mayModifyRegister(Inst, LFIBaseReg) || mayModifyRegister(Inst, LFITPReg)) |
| return error(Inst, "illegal modification of reserved LFI register"); |
| |
| if (isSyscall(Inst)) |
| return rewriteSyscall(Inst, Out, STI); |
| |
| if (isFSAccess(Inst)) |
| return rewriteFSAccess(Inst, Out, STI); |
| |
| // Pass through all other instructions unchanged. |
| Out.emitInstruction(Inst, STI); |
| } |
| |
| bool X86::X86MCLFIRewriter::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; |
| } |