Null Pointer Dereference in temp_load() when emulating MIPS32 code after a failed uc_emu_start
Version
Unicorn v2.1.4
Description
This bug crashes (segfaults) inside Unicorn when pwntools loads a MIPS32 ELF.
A null pointer dereference (segfault) occurs in temp_load() at tcg.c:3054 when calling uc_emu_start a second time after a first emulation that fails with UC_ERR_READ_UNMAPPED. The crash happens inside the TCG code generation path (tcg_gen_code_mipsel -> temp_load) when ts->mem_base is NULL.
The crash is triggered by emulating a MIPS32 (big-endian) ELF where an lw $t9, 0x8010($gp) instruction loads a controlled value (0x7c7c7c7c) from a GOT address into $t9, followed by a jalr $t9 that jumps to that invalid address. After the first emulation attempt fails on the unmapped jump target, calling uc_emu_start again at the subsequent instruction causes the null pointer dereference.
Backtrace
#0 temp_load (s=0x5555555668c0, ts=0x555555568928, desired_regs=<optimised out>,
allocated_regs=48, preferred_regs=64)
at /home/user/unicorn_crash/unicorn/qemu/tcg/tcg.c:3054
3054 tcg_out_ld(s, ts->type, reg, ts->mem_base->reg, ts->mem_offset);
(gdb) p ts->mem_base
$1 = (struct TCGTemp *) 0x0
(gdb) bt # [Minimized]
#0 temp_load ()
#1 tcg_gen_code_mipsel ()
#2 tb_gen_code_mipsel ()
#3 cpu_exec_mipsel ()
#4 resume_all_vcpus_mipsel ()
#5 uc_emu_start ()
The offending line is:
tcg_out_ld(s, ts->type, reg, ts->mem_base->reg, ts->mem_offset);
where ts->mem_base is NULL.
Reproducers
Two minimal reproducers are provided:
Python (requires unicorn)
import struct, logging
logging.disable(logging.CRITICAL)
import unicorn as U
GOT_ADDR = 0x411020; MAGIC = 0x7c7c7c7c
def le(v): return struct.pack('<I', v)
NOP = 0x00000000; JALR_T9 = 0x0320f809; LW_T9 = 0x8f998010
MOVE_T7_RA = 0x03e07825; LI_T8 = 0x24180012
uc = U.Uc(U.UC_ARCH_MIPS, U.UC_MODE_32)
uc.mem_map(0x400000, 0x20000, U.UC_PROT_ALL)
uc.mem_write(0x400000, b'\x00' * 0x20000)
uc.mem_write(GOT_ADDR, struct.pack('<I', MAGIC))
uc.reg_write(U.mips_const.UC_MIPS_REG_GP, 0x419010)
uc.mem_write(0x400b10, le(JALR_T9))
uc.mem_write(0x400b14, le(NOP))
# First emu - will fail
try: uc.emu_start(0x400b10, until=-1, count=5)
except: pass
# Second emu - this crashes
uc.emu_start(0x400b14, until=-1, count=5)
C (requires unicorn)
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unicorn/unicorn.h>
#define GOT_ADDR 0x411020
#define MAGIC 0x7c7c7c7c
#define BASE 0x400000
#define SIZE 0x20000
int main(void) {
uc_engine *uc;
uc_err err;
uint32_t jalr_t9 = 0x0320f809;
uint32_t nop = 0x00000000;
uint32_t gp_val = 0x419010;
uint32_t magic = MAGIC;
err = uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32, &uc);
if (err != UC_ERR_OK) {
printf("uc_open failed: %s\n", uc_strerror(err));
return 1;
}
err = uc_mem_map(uc, BASE, SIZE, UC_PROT_ALL);
if (err != UC_ERR_OK) {
printf("uc_mem_map failed: %s\n", uc_strerror(err));
return 1;
}
void *zeros = calloc(1, SIZE);
if (!zeros) { perror("calloc"); return 1; }
uc_mem_write(uc, BASE, zeros, SIZE);
free(zeros);
uc_mem_write(uc, GOT_ADDR, &magic, sizeof(magic));
uc_reg_write(uc, UC_MIPS_REG_GP, &gp_val);
uc_mem_write(uc, 0x400b10, &jalr_t9, sizeof(jalr_t9));
uc_mem_write(uc, 0x400b14, &nop, sizeof(nop));
// First emulation - expected to fail
err = uc_emu_start(uc, 0x400b10, (uint64_t)-1, 0, 5);
printf("First uc_emu_start returned: %s\n", uc_strerror(err));
fflush(stdout);
// Second emulation - crashes
printf("About to start second emulation...\n");
fflush(stdout);
err = uc_emu_start(uc, 0x400b14, (uint64_t)-1, 0, 5);
if (err != UC_ERR_OK) {
printf("Second uc_emu_start failed: %s\n", uc_strerror(err));
} else {
printf("Second uc_emu_start succeeded\n");
}
uc_close(uc);
return 0;
}
Steps to Reproduce
- Build unicorn from source (v2.1.4) inside unicorn/build.
- Compile the C reproducer:
gcc -I unicorn/include -o small_repro small_repro.c -L unicorn/build -lunicorn -lpthread -lm
- Run with the correct library path:
LD_LIBRARY_PATH=unicorn/build/ ./small_repro
- Observe the segfault
Null Pointer Dereference in
temp_load()when emulating MIPS32 code after a faileduc_emu_startVersion
Unicorn v2.1.4
Description
This bug crashes (segfaults) inside Unicorn when pwntools loads a MIPS32 ELF.
A null pointer dereference (segfault) occurs in
temp_load()attcg.c:3054when callinguc_emu_starta second time after a first emulation that fails withUC_ERR_READ_UNMAPPED. The crash happens inside the TCG code generation path (tcg_gen_code_mipsel->temp_load) whents->mem_baseis NULL.The crash is triggered by emulating a MIPS32 (big-endian) ELF where an
lw $t9, 0x8010($gp)instruction loads a controlled value (0x7c7c7c7c) from a GOT address into$t9, followed by ajalr $t9that jumps to that invalid address. After the first emulation attempt fails on the unmapped jump target, callinguc_emu_startagain at the subsequent instruction causes the null pointer dereference.Backtrace
The offending line is:
where
ts->mem_baseis NULL.Reproducers
Two minimal reproducers are provided:
Python (requires unicorn)
C (requires unicorn)
Steps to Reproduce