From c3142923191ead8633f33969e118871d85d19ad9 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 15 Jul 2020 21:46:50 +0200 Subject: [PATCH] UserspaceEmulator: Catch use-after-frees by tracking malloc/free :^) This patch introduces a "MallocTracer" to the UserspaceEmulator. If this object is present on the Emulator, it can be notified whenever the emulated program does a malloc() or free(). The notifications come in via a magic instruction sequence that we embed in the LibC malloc() and free() functions. The sequence is: "salc x2, push reg32 x2, pop reg32 x3" The data about the malloc/free operation is in the three pushes. We make sure the sequence is harmless when running natively. Memory accesses on MmapRegion are then audited to see if they fall inside a known-to-be-freed malloc chunk. If so, we complain loud and red in the debugger output. :^) This is very, very cool! :^) It's also a whole lot slower than before, since now we're auditing memory accesses against a new set of metadata. This will need to be optimized (and running in this mode should be opt-in, perhaps even a separate program, etc.) --- DevTools/UserspaceEmulator/CMakeLists.txt | 1 + DevTools/UserspaceEmulator/Emulator.cpp | 13 ++- DevTools/UserspaceEmulator/Emulator.h | 9 ++ DevTools/UserspaceEmulator/MallocTracer.cpp | 110 ++++++++++++++++++++ DevTools/UserspaceEmulator/MallocTracer.h | 64 ++++++++++++ DevTools/UserspaceEmulator/MmapRegion.cpp | 37 +++++++ DevTools/UserspaceEmulator/MmapRegion.h | 2 + DevTools/UserspaceEmulator/SoftCPU.cpp | 38 ++++++- DevTools/UserspaceEmulator/SoftCPU.h | 5 + 9 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 DevTools/UserspaceEmulator/MallocTracer.cpp create mode 100644 DevTools/UserspaceEmulator/MallocTracer.h diff --git a/DevTools/UserspaceEmulator/CMakeLists.txt b/DevTools/UserspaceEmulator/CMakeLists.txt index ad0e0c4fd1..87fca0f34a 100644 --- a/DevTools/UserspaceEmulator/CMakeLists.txt +++ b/DevTools/UserspaceEmulator/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES Emulator.cpp + MallocTracer.cpp MmapRegion.cpp SharedBufferRegion.cpp SimpleRegion.cpp diff --git a/DevTools/UserspaceEmulator/Emulator.cpp b/DevTools/UserspaceEmulator/Emulator.cpp index 8306ba00b5..e97a4d8591 100644 --- a/DevTools/UserspaceEmulator/Emulator.cpp +++ b/DevTools/UserspaceEmulator/Emulator.cpp @@ -66,6 +66,7 @@ Emulator::Emulator(const Vector& arguments, NonnullRefPtr e : m_elf(move(elf)) , m_cpu(*this) { + m_malloc_tracer = make(); ASSERT(!s_the); s_the = this; setup_stack(arguments); @@ -167,12 +168,20 @@ int Emulator::exec() return m_exit_status; } +bool Emulator::is_in_malloc_or_free() const +{ + auto symbol = m_elf->symbolicate(m_cpu.eip()); + return symbol.starts_with("malloc") || symbol.starts_with("free"); +} + +static pid_t s_pid = getpid(); + void Emulator::dump_backtrace() { u32 offset = 0; String symbol = m_elf->symbolicate(m_cpu.eip(), &offset); - printf("> %#08x %s +%#x\n", m_cpu.eip(), symbol.characters(), offset); + dbgprintf("==%d== %#08x %s +%#x\n", s_pid, m_cpu.eip(), symbol.characters(), offset); u32 frame_ptr = m_cpu.ebp(); while (frame_ptr) { @@ -181,7 +190,7 @@ void Emulator::dump_backtrace() return; symbol = m_elf->symbolicate(ret_ptr, &offset); if (!symbol.is_null()) - printf("> %#08x %s +%#x\n", ret_ptr, symbol.characters(), offset); + dbgprintf("==%d== %#08x %s +%#x\n", s_pid, ret_ptr, symbol.characters(), offset); frame_ptr = m_mmu.read32({ 0x20, frame_ptr }); } diff --git a/DevTools/UserspaceEmulator/Emulator.h b/DevTools/UserspaceEmulator/Emulator.h index 236b709999..d9cfc5e755 100644 --- a/DevTools/UserspaceEmulator/Emulator.h +++ b/DevTools/UserspaceEmulator/Emulator.h @@ -26,6 +26,7 @@ #pragma once +#include "MallocTracer.h" #include "SoftCPU.h" #include "SoftMMU.h" #include @@ -35,6 +36,8 @@ namespace UserspaceEmulator { +class MallocTracer; + class Emulator { public: static Emulator& the(); @@ -49,12 +52,18 @@ public: SoftMMU& mmu() { return m_mmu; } + MallocTracer* malloc_tracer() { return m_malloc_tracer; } + + bool is_in_malloc_or_free() const; + private: NonnullRefPtr m_elf; SoftMMU m_mmu; SoftCPU m_cpu; + OwnPtr m_malloc_tracer; + void setup_stack(const Vector& arguments); int virt$shbuf_create(int size, FlatPtr buffer); diff --git a/DevTools/UserspaceEmulator/MallocTracer.cpp b/DevTools/UserspaceEmulator/MallocTracer.cpp new file mode 100644 index 0000000000..ed24dafee7 --- /dev/null +++ b/DevTools/UserspaceEmulator/MallocTracer.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "MallocTracer.h" +#include "Emulator.h" +#include + +namespace UserspaceEmulator { + +static pid_t s_pid = getpid(); + +MallocTracer::MallocTracer() +{ +} + +void MallocTracer::target_did_malloc(Badge, FlatPtr address, size_t size) +{ + if (auto* existing_mallocation = find_mallocation(address)) { + ASSERT(existing_mallocation->freed); + existing_mallocation->size = size; + existing_mallocation->freed = false; + return; + } + m_mallocations.append({ address, size }); +} + +void MallocTracer::target_did_free(Badge, FlatPtr address) +{ + for (auto& mallocation : m_mallocations) { + if (mallocation.address == address) { + mallocation.freed = true; + return; + } + } + ASSERT_NOT_REACHED(); +} + +MallocTracer::Mallocation* MallocTracer::find_mallocation(FlatPtr address) +{ + for (auto& mallocation : m_mallocations) { + if (mallocation.contains(address)) + return &mallocation; + } + return nullptr; +} + +void MallocTracer::audit_read(FlatPtr address, size_t size) +{ + if (Emulator::the().is_in_malloc_or_free()) + return; + + auto* mallocation = find_mallocation(address); + if (!mallocation) + return; + + size_t offset_into_mallocation = address - mallocation->address; + + if (mallocation->freed) { + dbgprintf("\n"); + dbgprintf("==%d== \033[31;1mUAF\033[0m, invalid %zu-byte read at address %p\n", s_pid, size, address); + dbgprintf("==%d== Address is %zu bytes into freed block of size %zu\n", s_pid, offset_into_mallocation, mallocation->size); + Emulator::the().dump_backtrace(); + return; + } +} + +void MallocTracer::audit_write(FlatPtr address, size_t size) +{ + if (Emulator::the().is_in_malloc_or_free()) + return; + + auto* mallocation = find_mallocation(address); + if (!mallocation) + return; + + size_t offset_into_mallocation = address - mallocation->address; + + if (mallocation->freed) { + dbgprintf("\n"); + dbgprintf("==%d== \033[31;1mUAF\033[0m, invalid %zu-byte write at address %p\n", s_pid, size, address); + dbgprintf("==%d== Address is %zu bytes into freed block of size %zu\n", s_pid, offset_into_mallocation, mallocation->size); + Emulator::the().dump_backtrace(); + return; + } +} + +} diff --git a/DevTools/UserspaceEmulator/MallocTracer.h b/DevTools/UserspaceEmulator/MallocTracer.h new file mode 100644 index 0000000000..706b63fadc --- /dev/null +++ b/DevTools/UserspaceEmulator/MallocTracer.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +namespace UserspaceEmulator { + +class SoftCPU; + +class MallocTracer { +public: + MallocTracer(); + + void target_did_malloc(Badge, FlatPtr address, size_t); + void target_did_free(Badge, FlatPtr address); + + void audit_read(FlatPtr address, size_t); + void audit_write(FlatPtr address, size_t); + +private: + struct Mallocation { + bool contains(FlatPtr a) const + { + return a >= address && a < (address + size); + } + + FlatPtr address { 0 }; + size_t size { 0 }; + bool freed { false }; + }; + + Mallocation* find_mallocation(FlatPtr); + + Vector m_mallocations; +}; + +} diff --git a/DevTools/UserspaceEmulator/MmapRegion.cpp b/DevTools/UserspaceEmulator/MmapRegion.cpp index 18c65f1e0d..84cf8c3ec4 100644 --- a/DevTools/UserspaceEmulator/MmapRegion.cpp +++ b/DevTools/UserspaceEmulator/MmapRegion.cpp @@ -61,6 +61,13 @@ MmapRegion::~MmapRegion() free(m_data); } +bool MmapRegion::is_malloc_block() const +{ + // FIXME: This is obviously incomplete! + // We should somehow know which mmap regions are malloc blocks. + return !m_file_backed; +} + u8 MmapRegion::read8(FlatPtr offset) { if (!is_readable()) { @@ -69,6 +76,11 @@ u8 MmapRegion::read8(FlatPtr offset) TODO(); } + if (is_malloc_block()) { + if (auto* tracer = Emulator::the().malloc_tracer()) + tracer->audit_read(base() + offset, 1); + } + ASSERT(offset < size()); return *reinterpret_cast(m_data + offset); } @@ -81,6 +93,11 @@ u16 MmapRegion::read16(u32 offset) TODO(); } + if (is_malloc_block()) { + if (auto* tracer = Emulator::the().malloc_tracer()) + tracer->audit_read(base() + offset, 2); + } + ASSERT(offset + 1 < size()); return *reinterpret_cast(m_data + offset); } @@ -93,6 +110,11 @@ u32 MmapRegion::read32(u32 offset) TODO(); } + if (is_malloc_block()) { + if (auto* tracer = Emulator::the().malloc_tracer()) + tracer->audit_read(base() + offset, 4); + } + ASSERT(offset + 3 < size()); return *reinterpret_cast(m_data + offset); } @@ -105,6 +127,11 @@ void MmapRegion::write8(u32 offset, u8 value) TODO(); } + if (is_malloc_block()) { + if (auto* tracer = Emulator::the().malloc_tracer()) + tracer->audit_write(base() + offset, 1); + } + ASSERT(offset < size()); *reinterpret_cast(m_data + offset) = value; } @@ -117,6 +144,11 @@ void MmapRegion::write16(u32 offset, u16 value) TODO(); } + if (is_malloc_block()) { + if (auto* tracer = Emulator::the().malloc_tracer()) + tracer->audit_write(base() + offset, 2); + } + ASSERT(offset + 1 < size()); *reinterpret_cast(m_data + offset) = value; } @@ -129,6 +161,11 @@ void MmapRegion::write32(u32 offset, u32 value) TODO(); } + if (is_malloc_block()) { + if (auto* tracer = Emulator::the().malloc_tracer()) + tracer->audit_write(base() + offset, 4); + } + ASSERT(offset + 3 < size()); *reinterpret_cast(m_data + offset) = value; } diff --git a/DevTools/UserspaceEmulator/MmapRegion.h b/DevTools/UserspaceEmulator/MmapRegion.h index d88e325f0a..d169e1746d 100644 --- a/DevTools/UserspaceEmulator/MmapRegion.h +++ b/DevTools/UserspaceEmulator/MmapRegion.h @@ -51,6 +51,8 @@ public: bool is_writable() const { return m_prot & PROT_WRITE; } bool is_executable() const { return m_prot & PROT_EXEC; } + bool is_malloc_block() const; + private: MmapRegion(u32 base, u32 size, int prot); diff --git a/DevTools/UserspaceEmulator/SoftCPU.cpp b/DevTools/UserspaceEmulator/SoftCPU.cpp index 0d43a08e58..39dd220946 100644 --- a/DevTools/UserspaceEmulator/SoftCPU.cpp +++ b/DevTools/UserspaceEmulator/SoftCPU.cpp @@ -65,6 +65,19 @@ void SoftCPU::dump() const printf("o=%u s=%u z=%u a=%u p=%u c=%u\n", of(), sf(), zf(), af(), pf(), cf()); } +void SoftCPU::did_receive_secret_data() +{ + if (m_secret_data[0] == 1) { + if (auto* tracer = m_emulator.malloc_tracer()) + tracer->target_did_malloc({}, m_secret_data[2], m_secret_data[1]); + } else if (m_secret_data[0] == 2) { + if (auto* tracer = m_emulator.malloc_tracer()) + tracer->target_did_free({}, m_secret_data[1]); + } else { + ASSERT_NOT_REACHED(); + } +} + void SoftCPU::update_code_cache() { auto* region = m_emulator.mmu().find_region({ cs(), eip() }); @@ -604,7 +617,6 @@ ALWAYS_INLINE static T op_shrd(SoftCPU& cpu, T data, T extra_bits, u8 steps) return result; } - template ALWAYS_INLINE static T op_shld(SoftCPU& cpu, T data, T extra_bits, u8 steps) { @@ -633,7 +645,6 @@ ALWAYS_INLINE static T op_shld(SoftCPU& cpu, T data, T extra_bits, u8 steps) return result; } - template ALWAYS_INLINE void SoftCPU::generic_AL_imm8(Op op, const X86::Instruction& insn) { @@ -1477,6 +1488,18 @@ void SoftCPU::PUSH_reg16(const X86::Instruction&) { TODO(); } void SoftCPU::PUSH_reg32(const X86::Instruction& insn) { push32(gpr32(insn.reg32())); + + if (m_secret_handshake_state == 2) { + m_secret_data[0] = gpr32(insn.reg32()); + ++m_secret_handshake_state; + } else if (m_secret_handshake_state == 3) { + m_secret_data[1] = gpr32(insn.reg32()); + ++m_secret_handshake_state; + } else if (m_secret_handshake_state == 4) { + m_secret_data[2] = gpr32(insn.reg32()); + m_secret_handshake_state = 0; + did_receive_secret_data(); + } } void SoftCPU::RCL_RM16_1(const X86::Instruction&) { TODO(); } @@ -1534,7 +1557,16 @@ void SoftCPU::ROR_RM8_1(const X86::Instruction&) { TODO(); } void SoftCPU::ROR_RM8_CL(const X86::Instruction&) { TODO(); } void SoftCPU::ROR_RM8_imm8(const X86::Instruction&) { TODO(); } void SoftCPU::SAHF(const X86::Instruction&) { TODO(); } -void SoftCPU::SALC(const X86::Instruction&) { TODO(); } + +void SoftCPU::SALC(const X86::Instruction&) +{ + set_al(cf() ? 0x01 : 0x00); + + if (m_secret_handshake_state < 2) + ++m_secret_handshake_state; + else + m_secret_handshake_state = 0; +} template static T op_sar(SoftCPU& cpu, T data, u8 steps) diff --git a/DevTools/UserspaceEmulator/SoftCPU.h b/DevTools/UserspaceEmulator/SoftCPU.h index bb199b7992..d00963fe32 100644 --- a/DevTools/UserspaceEmulator/SoftCPU.h +++ b/DevTools/UserspaceEmulator/SoftCPU.h @@ -799,6 +799,8 @@ private: void update_code_cache(); + void did_receive_secret_data(); + private: Emulator& m_emulator; @@ -810,6 +812,9 @@ private: const u8* m_cached_code_ptr { nullptr }; const u8* m_cached_code_end { nullptr }; + + u32 m_secret_handshake_state { 0 }; + u32 m_secret_data[3]; }; ALWAYS_INLINE u8 SoftCPU::read8()