1
Fork 0
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:
Andreas Kling 2020-07-15 21:46:50 +02:00
parent d7c87e84f3
commit c314292319
9 changed files with 274 additions and 5 deletions

View file

@ -1,5 +1,6 @@
set(SOURCES
Emulator.cpp
MallocTracer.cpp
MmapRegion.cpp
SharedBufferRegion.cpp
SimpleRegion.cpp

View file

@ -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 });
}

View file

@ -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);

View 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;
}
}
}

View 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;
};
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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)

View file

@ -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()