[StopInfoMachException] Summarize arm64e BLRAx/LDRAx auth failures

Upstream lldb support for summarizing BLRAx and LDRAx auth failures.

rdar://41615322

Differential Revision: https://reviews.llvm.org/D102428
This commit is contained in:
Vedant Kumar 2019-11-14 13:12:46 -08:00
parent c4048d8f50
commit 66902a32c8
22 changed files with 414 additions and 0 deletions

View file

@ -210,6 +210,10 @@ public:
}
};
/// Write a description of this object to a Stream.
bool GetDescription(Stream &s, Target &target,
lldb::DescriptionLevel level) const;
/// Dump a description of this object to a Stream.
///
/// Dump a description of the contents of this object to the supplied stream

View file

@ -150,6 +150,10 @@ public:
virtual bool HasDelaySlot();
virtual bool IsLoad() = 0;
virtual bool IsAuthenticated() = 0;
bool CanSetBreakpoint ();
virtual size_t Decode(const Disassembler &disassembler,
@ -336,6 +340,10 @@ public:
bool HasDelaySlot() override;
bool IsLoad() override;
bool IsAuthenticated() override;
void CalculateMnemonicOperandsAndComment(
const ExecutionContext *exe_ctx) override {
// TODO: fill this in and put opcode name into Instruction::m_opcode_name,

View file

@ -389,6 +389,19 @@ bool Address::SetOpcodeLoadAddress(lldb::addr_t load_addr, Target *target,
return false;
}
bool Address::GetDescription(Stream &s, Target &target,
DescriptionLevel level) const {
assert(level == eDescriptionLevelBrief &&
"Non-brief descriptions not implemented");
LineEntry line_entry;
if (CalculateSymbolContextLineEntry(line_entry)) {
s.Printf(" (%s:%u:%u)", line_entry.file.GetFilename().GetCString(),
line_entry.line, line_entry.column);
return true;
}
return false;
}
bool Address::Dump(Stream *s, ExecutionContextScope *exe_scope, DumpStyle style,
DumpStyle fallback_style, uint32_t addr_size) const {
// If the section was nullptr, only load address is going to work unless we

View file

@ -1123,6 +1123,10 @@ bool PseudoInstruction::HasDelaySlot() {
return false;
}
bool PseudoInstruction::IsLoad() { return false; }
bool PseudoInstruction::IsAuthenticated() { return false; }
size_t PseudoInstruction::Decode(const lldb_private::Disassembler &disassembler,
const lldb_private::DataExtractor &data,
lldb::offset_t data_offset) {

View file

@ -61,6 +61,8 @@ public:
bool CanBranch(llvm::MCInst &mc_inst) const;
bool HasDelaySlot(llvm::MCInst &mc_inst) const;
bool IsCall(llvm::MCInst &mc_inst) const;
bool IsLoad(llvm::MCInst &mc_inst) const;
bool IsAuthenticated(llvm::MCInst &mc_inst) const;
private:
MCDisasmInstance(std::unique_ptr<llvm::MCInstrInfo> &&instr_info_up,
@ -102,6 +104,16 @@ public:
return m_has_delay_slot;
}
bool IsLoad() override {
VisitInstruction();
return m_is_load;
}
bool IsAuthenticated() override {
VisitInstruction();
return m_is_authenticated;
}
DisassemblerLLVMC::MCDisasmInstance *GetDisasmToUse(bool &is_alternate_isa) {
DisassemblerScope disasm(*this);
return GetDisasmToUse(is_alternate_isa, disasm);
@ -817,9 +829,13 @@ protected:
// - Might branch
// - Does not have a delay slot
// - Is not a call
// - Is not a load
// - Is not an authenticated instruction
bool m_does_branch = true;
bool m_has_delay_slot = false;
bool m_is_call = false;
bool m_is_load = false;
bool m_is_authenticated = false;
void VisitInstruction() {
if (m_has_visited_instruction)
@ -849,6 +865,8 @@ protected:
m_does_branch = mc_disasm_ptr->CanBranch(inst);
m_has_delay_slot = mc_disasm_ptr->HasDelaySlot(inst);
m_is_call = mc_disasm_ptr->IsCall(inst);
m_is_load = mc_disasm_ptr->IsLoad(inst);
m_is_authenticated = mc_disasm_ptr->IsAuthenticated(inst);
}
private:
@ -1027,6 +1045,27 @@ bool DisassemblerLLVMC::MCDisasmInstance::IsCall(llvm::MCInst &mc_inst) const {
return m_instr_info_up->get(mc_inst.getOpcode()).isCall();
}
bool DisassemblerLLVMC::MCDisasmInstance::IsLoad(llvm::MCInst &mc_inst) const {
return m_instr_info_up->get(mc_inst.getOpcode()).mayLoad();
}
bool DisassemblerLLVMC::MCDisasmInstance::IsAuthenticated(
llvm::MCInst &mc_inst) const {
auto InstrDesc = m_instr_info_up->get(mc_inst.getOpcode());
// Treat software auth traps (brk 0xc470 + aut key, where 0x70 == 'p', 0xc4
// == 'a' + 'c') as authenticated instructions for reporting purposes, in
// addition to the standard authenticated instructions specified in ARMv8.3.
bool IsBrkC47x = false;
if (InstrDesc.isTrap() && mc_inst.getNumOperands() == 1) {
const llvm::MCOperand &Op0 = mc_inst.getOperand(0);
if (Op0.isImm() && Op0.getImm() >= 0xc470 && Op0.getImm() <= 0xc474)
IsBrkC47x = true;
}
return InstrDesc.isAuthenticated() || IsBrkC47x;
}
DisassemblerLLVMC::DisassemblerLLVMC(const ArchSpec &arch,
const char *flavor_string)
: Disassembler(arch, flavor_string), m_exe_ctx(nullptr), m_inst(nullptr),

View file

@ -17,6 +17,7 @@
#include "lldb/Breakpoint/Watchpoint.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/ABI.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
@ -30,6 +31,182 @@
using namespace lldb;
using namespace lldb_private;
/// Information about a pointer-authentication related instruction.
struct PtrauthInstructionInfo {
bool IsAuthenticated;
bool IsLoad;
bool DoesBranch;
};
/// Get any pointer-authentication related information about the instruction
/// at address \p at_addr.
static llvm::Optional<PtrauthInstructionInfo>
GetPtrauthInstructionInfo(Target &target, const ArchSpec &arch,
const Address &at_addr) {
const char *plugin_name = nullptr;
const char *flavor = nullptr;
AddressRange range_bounds(at_addr, 4);
const bool prefer_file_cache = true;
DisassemblerSP disassembler_sp = Disassembler::DisassembleRange(
arch, plugin_name, flavor, target, range_bounds, prefer_file_cache);
if (!disassembler_sp)
return llvm::None;
InstructionList &insn_list = disassembler_sp->GetInstructionList();
InstructionSP insn = insn_list.GetInstructionAtIndex(0);
if (!insn)
return llvm::None;
return PtrauthInstructionInfo{insn->IsAuthenticated(), insn->IsLoad(),
insn->DoesBranch()};
}
/// Describe the load address of \p addr using the format filename:line:col.
static void DescribeAddressBriefly(Stream &strm, const Address &addr,
Target &target) {
strm.Printf("at address=0x%" PRIx64, addr.GetLoadAddress(&target));
StreamString s;
if (addr.GetDescription(s, target, eDescriptionLevelBrief))
strm.Printf(" %s", s.GetString().data());
strm.Printf(".\n");
}
bool StopInfoMachException::DeterminePtrauthFailure(ExecutionContext &exe_ctx) {
bool IsBreakpoint = m_value == 6; // EXC_BREAKPOINT
bool IsBadAccess = m_value == 1; // EXC_BAD_ACCESS
if (!IsBreakpoint && !IsBadAccess)
return false;
// Check that we have a live process.
if (!exe_ctx.HasProcessScope() || !exe_ctx.HasThreadScope() ||
!exe_ctx.HasTargetScope())
return false;
Thread &thread = *exe_ctx.GetThreadPtr();
StackFrameSP current_frame = thread.GetStackFrameAtIndex(0);
if (!current_frame)
return false;
Target &target = *exe_ctx.GetTargetPtr();
Process &process = *exe_ctx.GetProcessPtr();
ABISP abi_sp = process.GetABI();
const ArchSpec &arch = target.GetArchitecture();
assert(abi_sp && "Missing ABI info");
// Check for a ptrauth-enabled target.
const bool ptrauth_enabled_target =
arch.GetCore() == ArchSpec::eCore_arm_arm64e;
if (!ptrauth_enabled_target)
return false;
// Set up a stream we can write a diagnostic into.
StreamString strm;
auto emit_ptrauth_prologue = [&](uint64_t at_address) {
strm.Printf("EXC_BAD_ACCESS (code=%" PRIu64 ", address=0x%" PRIx64 ")\n",
m_exc_code, at_address);
strm.Printf("Note: Possible pointer authentication failure detected.\n");
};
// Check if we have a "brk 0xc47x" trap, where the value that failed to
// authenticate is in x16.
Address current_address = current_frame->GetFrameCodeAddress();
if (IsBreakpoint) {
RegisterContext *reg_ctx = exe_ctx.GetRegisterContext();
if (!reg_ctx)
return false;
const RegisterInfo *X16Info = reg_ctx->GetRegisterInfoByName("x16");
RegisterValue X16Val;
if (!reg_ctx->ReadRegister(X16Info, X16Val))
return false;
uint64_t bad_address = X16Val.GetAsUInt64();
uint64_t fixed_bad_address = abi_sp->FixCodeAddress(bad_address);
Address brk_address;
if (!target.ResolveLoadAddress(fixed_bad_address, brk_address))
return false;
auto brk_ptrauth_info =
GetPtrauthInstructionInfo(target, arch, current_address);
if (brk_ptrauth_info && brk_ptrauth_info->IsAuthenticated) {
emit_ptrauth_prologue(bad_address);
strm.Printf("Found value that failed to authenticate ");
DescribeAddressBriefly(strm, brk_address, target);
m_description = std::string(strm.GetString());
return true;
}
return false;
}
assert(IsBadAccess && "Handle EXC_BAD_ACCESS only after this point");
// Check that we have the "bad address" from an EXC_BAD_ACCESS.
if (m_exc_data_count < 2)
return false;
// Ok, we know the Target is valid and that it describes a ptrauth-enabled
// device. Now, we need to determine whether this exception was caused by a
// ptrauth failure.
uint64_t bad_address = m_exc_subcode;
uint64_t fixed_bad_address = abi_sp->FixCodeAddress(bad_address);
uint64_t current_pc = current_address.GetLoadAddress(&target);
// Detect: LDRAA, LDRAB (Load Register, with pointer authentication).
//
// If an authenticated load results in an exception, the instruction at the
// current PC should be one of LDRAx.
if (bad_address != current_pc && fixed_bad_address != current_pc) {
auto ptrauth_info =
GetPtrauthInstructionInfo(target, arch, current_address);
if (ptrauth_info && ptrauth_info->IsAuthenticated && ptrauth_info->IsLoad) {
emit_ptrauth_prologue(bad_address);
strm.Printf("Found authenticated load instruction ");
DescribeAddressBriefly(strm, current_address, target);
m_description = std::string(strm.GetString());
return true;
}
}
// Detect: BLRAA, BLRAAZ, BLRAB, BLRABZ (Branch with Link to Register, with
// pointer authentication).
//
// TODO: Detect: BRAA, BRAAZ, BRAB, BRABZ (Branch to Register, with pointer
// authentication). At a minimum, this requires call site info support for
// indirect calls.
//
// If an authenticated call or tail call results in an exception, stripping
// the bad address should give the current PC, which points to the address
// we tried to branch to.
if (bad_address != current_pc && fixed_bad_address == current_pc) {
if (StackFrameSP parent_frame = thread.GetStackFrameAtIndex(1)) {
addr_t return_pc =
parent_frame->GetFrameCodeAddress().GetLoadAddress(&target);
Address blr_address;
if (!target.ResolveLoadAddress(return_pc - 4, blr_address))
return false;
auto blr_ptrauth_info =
GetPtrauthInstructionInfo(target, arch, blr_address);
if (blr_ptrauth_info && blr_ptrauth_info->IsAuthenticated &&
blr_ptrauth_info->DoesBranch) {
emit_ptrauth_prologue(bad_address);
strm.Printf("Found authenticated indirect branch ");
DescribeAddressBriefly(strm, blr_address, target);
m_description = std::string(strm.GetString());
return true;
}
}
}
// TODO: Detect: RETAA, RETAB (Return from subroutine, with pointer
// authentication).
//
// Is there a motivating, non-malicious code snippet that corrupts LR?
return false;
}
const char *StopInfoMachException::GetDescription() {
if (!m_description.empty())
return m_description.c_str();
@ -79,6 +256,11 @@ const char *StopInfoMachException::GetDescription() {
}
break;
case llvm::Triple::aarch64:
if (DeterminePtrauthFailure(exe_ctx))
return m_description.c_str();
break;
default:
break;
}
@ -190,6 +372,11 @@ const char *StopInfoMachException::GetDescription() {
}
break;
case llvm::Triple::aarch64:
if (DeterminePtrauthFailure(exe_ctx))
return m_description.c_str();
break;
default:
break;
}

View file

@ -16,6 +16,11 @@
namespace lldb_private {
class StopInfoMachException : public StopInfo {
/// Determine the pointer-authentication related failure that caused this
/// exception. Returns true and fills out the failure description if there
/// is auth-related failure, and returns false otherwise.
bool DeterminePtrauthFailure(ExecutionContext &exe_ctx);
public:
// Constructors and Destructors
StopInfoMachException(Thread &thread, uint32_t exc_type,

View file

@ -0,0 +1,2 @@
C_SOURCES := blraa.c
include Makefile.rules

View file

@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators
lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipIf(archs=decorators.no_match(['arm64e']))])

View file

@ -0,0 +1,28 @@
void foo() {}
int main() {
//% self.filecheck("c", "blraa.c")
// CHECK: stop reason = EXC_BAD_ACCESS
// CHECK-NEXT: Note: Possible pointer authentication failure detected.
// CHECK-NEXT: Found authenticated indirect branch at address=0x{{.*}} (blraa.c:[[@LINE+1]]:3).
asm volatile (
"mov x9, #0xbad \n"
"blraa %[target], x9 \n"
/* Outputs */ :
/* Inputs */ : [target] "r"(&foo)
/* Clobbers */ : "x9"
);
return 1;
}
// Expected codegen and exception message without ptrauth diagnostics:
// * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x2000000100007f9c)
// frame #0: 0x0000000100007f9c blraa2`foo
// blraa2`foo:
// 0x100007f9c <+0>: ret
//
// blraa2`main:
// 0x100007fa0 <+0>: nop
// 0x100007fa4 <+4>: ldr x8, #0x5c
// 0x100007fa8 <+8>: mov x9, #0xbad

View file

@ -0,0 +1,2 @@
C_SOURCES := braa.c
include Makefile.rules

View file

@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators
lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipIf(archs=decorators.no_match(['arm64e']))])

View file

@ -0,0 +1,29 @@
void foo() {}
int main() {
//% self.filecheck("c", "braa.c")
// CHECK: stop reason = EXC_BAD_ACCESS
//
// TODO: We need call site info support for indirect calls to make this work.
// CHECK-NOT: pointer authentication failure
asm volatile (
"mov x9, #0xbad \n"
"braa %[target], x9 \n"
/* Outputs */ :
/* Inputs */ : [target] "r"(&foo)
/* Clobbers */ : "x9"
);
return 1;
}
// Expected codegen and exception message without ptrauth diagnostics:
// * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x2000000100007f9c)
// frame #0: 0x0000000100007f9c braa`foo
// braa`foo:
// 0x100007f9c <+0>: ret
//
// braa`main:
// 0x100007fa0 <+0>: nop
// 0x100007fa4 <+4>: ldr x8, #0x5c
// 0x100007fa8 <+8>: mov x9, #0xbad

View file

@ -0,0 +1,2 @@
C_SOURCES := ldraa.c
include Makefile.rules

View file

@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators
lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipIf(archs=decorators.no_match(['arm64e']))])

View file

@ -0,0 +1,31 @@
int main() {
//% self.filecheck("c", "ldraa.c")
// CHECK: EXC_BAD_ACCESS
// CHECK-NEXT: Note: Possible pointer authentication failure detected.
// CHECK-NEXT: Found authenticated load instruction at address=0x{{.*}} (ldraa.c:[[@LINE+3]]:3).
long long foo = 0;
asm volatile (
"ldraa x9, [%[target]] \n"
/* Outputs */ :
/* Inputs */ : [target] "r"(&foo)
/* Clobbers */ :
);
return 1;
}
// Expected codegen, register state, and exception message without ptrauth diagnostics:
// * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x2000016fdffc38)
// frame #0: 0x0000000100007fa8 ldraa`main + 12
// ldraa`main:
// -> 0x100007fa8 <+12>: ldraa x9, [x8]
// 0x100007fac <+16>: orr w0, wzr, #0x1
// 0x100007fb0 <+20>: add sp, sp, #0x10 ; =0x10
// 0x100007fb4 <+24>: ret
// Target 0: (ldraa) stopped.
// (lldb) p/x $x8
// (unsigned long) $0 = 0x000000016fdffc38
// (lldb) x/8 $x8
// 0x16fdffc38: 0x00000000 0x00000000 0x80254f30 0x00000001
// 0x16fdffc48: 0x00000000 0x00000000 0x00000000 0x00000000

View file

@ -0,0 +1,2 @@
C_SOURCES := brkC47x.c
include Makefile.rules

View file

@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators
lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipIf(archs=decorators.no_match(['arm64e']))])

View file

@ -0,0 +1,17 @@
void foo() {}
int main() {
//% self.filecheck("c", "brkC47x.c")
// CHECK: stop reason = EXC_BAD_ACCESS
// CHECK-NEXT: Note: Possible pointer authentication failure detected.
// CHECK-NEXT: Found value that failed to authenticate at address=0x{{.*}} (brkC47x.c:1:13).
asm volatile (
"mov x16, %[target] \n"
"brk 0xc470 \n"
/* Outputs */ :
/* Inputs */ : [target] "r"(&foo)
/* Clobbers */ : "x16"
);
return 1;
}

View file

@ -0,0 +1,2 @@
C_SOURCES := brkC47x.c
include Makefile.rules

View file

@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators
lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipIf(archs=decorators.no_match(['arm64e']))])

View file

@ -0,0 +1,14 @@
int main() {
//% self.filecheck("c", "brkC47x.c")
// CHECK: stop reason = EXC_BAD_ACCESS
// CHECK-NOT: Note: Possible pointer authentication failure detected.
asm volatile (
"mov x16, #0xbad \n"
"brk 0xc470 \n"
/* Outputs */ :
/* Inputs */ :
/* Clobbers */ : "x16"
);
return 1;
}