This is a robustness bug in the spirit of #1766 ("The code should return errors instead of aborting/exitting"):
uc_context_alloc (uc.c:2241) calls g_malloc(size) and only explicitly zeros fv (uc.c:2246). snapshot_level, ramblock_freed, and
last_block stay uninitialized. If a caller does uc_context_alloc + uc_context_restore without an intervening uc_context_save, restore happily writes those garbage values into the engine (uc.c:2567/2578/2579) and returns OK. The next emulation hits g_assert(ret < cpu->num_ases && ret >= 0) in cpu_asidx_from_attrs (qemu/include/hw/core/cpu.h:421) and aborts the host process.
A Python user sees the interpreter abort with no Python-side exception.
Repro
/* repro_ctx_restore_no_save.c */
#include <stdio.h>
#include <unicorn/unicorn.h>
int main(void) {
uc_engine *uc;
uc_open(UC_ARCH_X86, UC_MODE_64, &uc);
uc_context *ctx;
uc_context_alloc(uc, &ctx);
uc_context_restore(uc, ctx); /* returns UC_ERR_OK silently */
uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL);
uint8_t nop = 0x90;
uc_mem_write(uc, 0x1000, &nop, 1);
uc_emu_start(uc, 0x1000, 0x1001, 0, 1);
}
Build the same way as a stock ASan debug build:
CC=clang CFLAGS="-fsanitize=address,undefined -g -O1" \
cmake .. -DUNICORN_ARCH=x86 -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Debug
cmake --build . -j
clang -fsanitize=address,undefined -g -O1 -I../include \
repro_ctx_restore_no_save.c libunicorn.a -lpthread -o repro
./repro
Output:
calling uc_context_restore WITHOUT prior uc_context_save...
ret = OK (UC_ERR_OK)
Assertion failed: (ret < cpu->num_ases && ret >= 0),
function cpu_asidx_from_attrs, file cpu.h, line 421.
Reproduces both with apple clang and homebrew clang ASan-only builds.
Suggested fix
Change g_malloc(size) to g_malloc0(size) at uc.c:2241. One line. The explicit fv = NULL at 2246 then becomes redundant but harmless.
If you'd prefer to keep g_malloc for the variable-length body for any reason, zero just the header bytes (sizeof uc_context minus data[])
before returning.
A more conservative variant is to set a "saved" flag in the header in uc_context_save and refuse uc_context_restore (return UC_ERR_ARG) if the flag is unset. That also closes #1766 for this entry point.
Happy to send the patch + a tests/unit/test_ctl.c regression test against dev.
This is a robustness bug in the spirit of #1766 ("The code should return errors instead of aborting/exitting"):
uc_context_alloc(uc.c:2241) callsg_malloc(size)and only explicitly zerosfv(uc.c:2246).snapshot_level,ramblock_freed, andlast_blockstay uninitialized. If a caller doesuc_context_alloc + uc_context_restorewithout an interveninguc_context_save, restore happily writes those garbage values into the engine (uc.c:2567/2578/2579) and returns OK. The next emulation hitsg_assert(ret < cpu->num_ases && ret >= 0)incpu_asidx_from_attrs(qemu/include/hw/core/cpu.h:421) and aborts the host process.A Python user sees the interpreter abort with no Python-side exception.
Repro
Build the same way as a stock ASan debug build:
Output:
Reproduces both with apple clang and homebrew clang ASan-only builds.
Suggested fix
Change
g_malloc(size)tog_malloc0(size)at uc.c:2241. One line. The explicitfv = NULLat 2246 then becomes redundant but harmless.If you'd prefer to keep
g_mallocfor the variable-length body for any reason, zero just the header bytes (sizeofuc_contextminusdata[])before returning.
A more conservative variant is to set a "saved" flag in the header in
uc_context_saveand refuseuc_context_restore(returnUC_ERR_ARG) if the flag is unset. That also closes #1766 for this entry point.Happy to send the patch + a
tests/unit/test_ctl.cregression test againstdev.