mirror of
https://github.com/RGBCube/serenity
synced 2025-06-01 12:58:13 +00:00
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.)
This commit is contained in:
parent
d7c87e84f3
commit
c314292319
9 changed files with 274 additions and 5 deletions
|
@ -1,5 +1,6 @@
|
|||
set(SOURCES
|
||||
Emulator.cpp
|
||||
MallocTracer.cpp
|
||||
MmapRegion.cpp
|
||||
SharedBufferRegion.cpp
|
||||
SimpleRegion.cpp
|
||||
|
|
|
@ -66,6 +66,7 @@ Emulator::Emulator(const Vector<String>& arguments, NonnullRefPtr<ELF::Loader> e
|
|||
: m_elf(move(elf))
|
||||
, m_cpu(*this)
|
||||
{
|
||||
m_malloc_tracer = make<MallocTracer>();
|
||||
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 });
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "MallocTracer.h"
|
||||
#include "SoftCPU.h"
|
||||
#include "SoftMMU.h"
|
||||
#include <AK/Types.h>
|
||||
|
@ -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<ELF::Loader> m_elf;
|
||||
|
||||
SoftMMU m_mmu;
|
||||
SoftCPU m_cpu;
|
||||
|
||||
OwnPtr<MallocTracer> m_malloc_tracer;
|
||||
|
||||
void setup_stack(const Vector<String>& arguments);
|
||||
|
||||
int virt$shbuf_create(int size, FlatPtr buffer);
|
||||
|
|
110
DevTools/UserspaceEmulator/MallocTracer.cpp
Normal file
110
DevTools/UserspaceEmulator/MallocTracer.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* 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 <AK/LogStream.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
static pid_t s_pid = getpid();
|
||||
|
||||
MallocTracer::MallocTracer()
|
||||
{
|
||||
}
|
||||
|
||||
void MallocTracer::target_did_malloc(Badge<SoftCPU>, 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<SoftCPU>, 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
64
DevTools/UserspaceEmulator/MallocTracer.h
Normal file
64
DevTools/UserspaceEmulator/MallocTracer.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* 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 <AK/Badge.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class SoftCPU;
|
||||
|
||||
class MallocTracer {
|
||||
public:
|
||||
MallocTracer();
|
||||
|
||||
void target_did_malloc(Badge<SoftCPU>, FlatPtr address, size_t);
|
||||
void target_did_free(Badge<SoftCPU>, 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<Mallocation> m_mallocations;
|
||||
};
|
||||
|
||||
}
|
|
@ -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<const u8*>(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<const u16*>(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<const u32*>(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<u8*>(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<u16*>(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<u32*>(m_data + offset) = value;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<typename T>
|
||||
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<bool update_dest, typename Op>
|
||||
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<typename T>
|
||||
static T op_sar(SoftCPU& cpu, T data, u8 steps)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue