$OpenBSD: patch-lib_Target_X86_X86FixupGadgets_cpp,v 1.1 2018/07/06 06:55:10 ajacoutot Exp $

Add a clang pass that identifies potential ROP gadgets and replaces ROP
friendly instructions with safe alternatives. This initial commit fixes
3 instruction forms that will lower to include a c3 (return) byte.
Additional problematic instructions can be fixed incrementally using
this framework.

Index: lib/Target/X86/X86FixupGadgets.cpp
--- lib/Target/X86/X86FixupGadgets.cpp.orig
+++ lib/Target/X86/X86FixupGadgets.cpp
@@ -0,0 +1,267 @@
+//===-- X86FixupGadgets.cpp - Fixup Instructions that make ROP Gadgets ----===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// This file defines a function pass that checks instructions for sequences
+/// that will lower to a potentially useful ROP gadget, and attempts to
+/// replace those sequences with alternatives that are not useful for ROP.
+///
+//===----------------------------------------------------------------------===//
+
+#include "X86.h"
+#include "X86InstrBuilder.h"
+#include "X86InstrInfo.h"
+#include "X86MachineFunctionInfo.h"
+#include "X86Subtarget.h"
+#include "X86TargetMachine.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/MachineRegisterInfo.h"
+#include "llvm/CodeGen/Passes.h"
+#include "llvm/MC/MCAsmInfo.h"
+#include "llvm/MC/MCSymbol.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/raw_ostream.h"
+using namespace llvm;
+
+#define FIXUPGADGETS_DESC "X86 ROP Gadget Fixup"
+#define FIXUPGADGETS_NAME "x86-fixup-gadgets"
+
+#define DEBUG_TYPE FIXUPGADGETS_NAME
+
+// Toggle with cc1 option: -backend-option -x86-fixup-gadgets=<true|false>
+static cl::opt<bool> FixupGadgets(
+    "x86-fixup-gadgets",
+    cl::desc("Replace ROP friendly instructions with alternatives"),
+    cl::init(true));
+
+namespace {
+class FixupGadgetsPass : public MachineFunctionPass {
+
+public:
+  static char ID;
+
+  StringRef getPassName() const override { return FIXUPGADGETS_DESC; }
+
+  FixupGadgetsPass()
+      : MachineFunctionPass(ID), STI(nullptr), TII(nullptr), TRI(nullptr) {}
+
+  /// Loop over all the instructions and replace ROP friendly
+  /// seuqences with less ROP friendly alternatives
+  bool runOnMachineFunction(MachineFunction &MF) override;
+
+  MachineFunctionProperties getRequiredProperties() const override {
+    return MachineFunctionProperties().set(
+        MachineFunctionProperties::Property::NoVRegs);
+  }
+
+private:
+  const X86Subtarget *STI;
+  const X86InstrInfo *TII;
+  const X86RegisterInfo *TRI;
+  bool Is64Bit;
+  enum InstrType {
+    NoType = 0,
+    OneGPRegC3,
+    TwoGPRegC3,
+    ThreeGPRegC3,
+  };
+
+  /// If an Instr has a ROP friendly construct, return it
+  InstrType isROPFriendly(MachineInstr &MI) const;
+
+  /// Helper functions for various kinds of instructions
+  bool isOneGPRegC3(MachineInstr &MI) const;
+  bool isTwoGPRegC3(MachineInstr &MI) const;
+  bool isThreeGPRegC3(MachineInstr &MI) const;
+
+  /// Replace ROP friendly instructions with safe alternatives
+  bool fixupInstruction(MachineFunction &MF, MachineBasicBlock &MBB,
+                        MachineInstr &MI, InstrType type);
+};
+char FixupGadgetsPass::ID = 0;
+} // namespace
+
+FunctionPass *llvm::createX86FixupGadgetsPass() {
+  return new FixupGadgetsPass();
+}
+
+bool FixupGadgetsPass::isOneGPRegC3(MachineInstr &MI) const {
+  MachineOperand &MO = MI.getOperand(0);
+  return MO.isReg() && MO.getReg() == X86::EBX &&
+         (MI.getDesc().TSFlags & X86II::FormMask) == X86II::MRMDestReg;
+}
+
+bool FixupGadgetsPass::isTwoGPRegC3(MachineInstr &MI) const {
+  bool DestSet = false;
+  bool SrcSet = false;
+  bool OpcodeSet = false;
+  MachineOperand &MO0 = MI.getOperand(0);
+  MachineOperand &MO1 = MI.getOperand(1);
+  if (!(MO0.isReg() && MO1.isReg()))
+    return false;
+
+  unsigned dstReg = MO0.getReg();
+  if (dstReg == X86::RBX || dstReg == X86::EBX || dstReg == X86::BX ||
+      dstReg == X86::BL)
+    DestSet = true;
+
+  if (!DestSet)
+    return false;
+
+  unsigned srcReg = MO1.getReg();
+  if (srcReg == X86::RAX || srcReg == X86::EAX || srcReg == X86::AX ||
+      srcReg == X86::AL)
+    SrcSet = true;
+
+  if (!SrcSet)
+    return false;
+
+  if ((MI.getDesc().TSFlags & X86II::FormMask) == X86II::MRMDestReg)
+    OpcodeSet = true;
+
+  return DestSet && SrcSet && OpcodeSet;
+}
+
+bool FixupGadgetsPass::isThreeGPRegC3(MachineInstr &MI) const {
+  bool DestSet = false;
+  bool SrcSet = false;
+  bool OpcodeSet = false;
+
+  MachineOperand &MO0 = MI.getOperand(0);
+  MachineOperand &MO1 = MI.getOperand(1);
+  MachineOperand &MO2 = MI.getOperand(2);
+  if (!(MO0.isReg() && MO1.isReg() && MO2.isReg() &&
+        MO0.getReg() == MO1.getReg()))
+    return false;
+
+  unsigned dstReg = MO0.getReg();
+  if (dstReg == X86::RBX || dstReg == X86::EBX || dstReg == X86::BX ||
+      dstReg == X86::BL)
+    DestSet = true;
+
+  if (!DestSet)
+    return false;
+
+  unsigned srcReg = MO2.getReg();
+  if (srcReg == X86::RAX || srcReg == X86::EAX || srcReg == X86::AX ||
+      srcReg == X86::AL)
+    SrcSet = true;
+
+  if (!SrcSet)
+    return false;
+
+  if ((MI.getDesc().TSFlags & X86II::FormMask) == X86II::MRMDestReg)
+    OpcodeSet = true;
+
+  return DestSet && SrcSet && OpcodeSet;
+}
+
+FixupGadgetsPass::InstrType
+FixupGadgetsPass::isROPFriendly(MachineInstr &MI) const {
+  switch (MI.getNumExplicitOperands()) {
+  case 1:
+    return isOneGPRegC3(MI) ? OneGPRegC3 : NoType;
+  case 2:
+    return isTwoGPRegC3(MI) ? TwoGPRegC3 : NoType;
+  case 3:
+    return isThreeGPRegC3(MI) ? ThreeGPRegC3 : NoType;
+  }
+  return NoType;
+}
+
+bool FixupGadgetsPass::fixupInstruction(MachineFunction &MF,
+                                        MachineBasicBlock &MBB,
+                                        MachineInstr &MI, InstrType type) {
+
+  if (type == NoType)
+    return false;
+
+  MachineOperand *MO0, *MO1, *MO2;
+  DebugLoc DL = MI.getDebugLoc();
+  unsigned XCHG = Is64Bit ? X86::XCHG64rr : X86::XCHG32rr;
+  unsigned SREG = Is64Bit ? X86::RAX : X86::EAX;
+  unsigned DREG = Is64Bit ? X86::RBX : X86::EBX;
+  unsigned tmpReg;
+
+  // Swap the two registers to start
+  BuildMI(MBB, MI, DL, TII->get(XCHG), DREG).addReg(DREG).addReg(SREG);
+
+  switch (type) {
+  case OneGPRegC3:
+    MO0 = &MI.getOperand(0);
+    switch (MO0->getReg()) {
+    case X86::RBX:
+      tmpReg = X86::RAX;
+      break;
+    case X86::EBX:
+      tmpReg = X86::EAX;
+      break;
+    case X86::BX:
+      tmpReg = X86::AX;
+      break;
+    case X86::BL:
+      tmpReg = X86::AL;
+      break;
+    default:
+      llvm_unreachable("Unknown DestReg in OneGPRegC3 fixup");
+    }
+    BuildMI(MBB, MI, DL, MI.getDesc(), tmpReg);
+    break;
+  case TwoGPRegC3:
+    MO0 = &MI.getOperand(0);
+    MO1 = &MI.getOperand(1);
+    BuildMI(MBB, MI, DL, MI.getDesc(), MO1->getReg()).addReg(MO0->getReg());
+    break;
+  case ThreeGPRegC3:
+    // Swap args around and set new dest reg
+    MO0 = &MI.getOperand(0); // Destination
+    MO2 = &MI.getOperand(2); // Source 2 == Other
+    BuildMI(MBB, MI, DL, MI.getDesc(), MO2->getReg())
+        .addReg(MO2->getReg())
+        .addReg(MO0->getReg());
+    break;
+  default:
+    llvm_unreachable("Unknown FixupGadgets Instruction Type");
+  }
+
+  // And swap them back to finish
+  BuildMI(MBB, MI, DL, TII->get(XCHG), DREG).addReg(DREG).addReg(SREG);
+  // Erase original instruction
+  MI.eraseFromParent();
+
+  return true;
+}
+
+bool FixupGadgetsPass::runOnMachineFunction(MachineFunction &MF) {
+  if (!FixupGadgets)
+    return false;
+
+  STI = &MF.getSubtarget<X86Subtarget>();
+  TII = STI->getInstrInfo();
+  TRI = STI->getRegisterInfo();
+  Is64Bit = STI->is64Bit();
+  std::vector<std::pair<MachineInstr *, InstrType>> fixups;
+  InstrType type;
+
+  bool modified = false;
+
+  for (auto &MBB : MF) {
+    fixups.clear();
+    for (auto &MI : MBB) {
+      type = isROPFriendly(MI);
+      if (type != NoType)
+        fixups.push_back(std::make_pair(&MI, type));
+    }
+    for (auto &fixup : fixups)
+      modified |= fixupInstruction(MF, MBB, *fixup.first, fixup.second);
+  }
+
+  return modified;
+}
