Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/uc_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,17 @@ struct uc_context {
bool ramblock_freed; // wheter there was a some ramblock freed
RAMBlock *last_block; // The last element of the ramblock list
FlatView *fv; // The current flatview of the memory
// Mirrors uc->context_content at uc_context_save() time. 0 means
// the context was allocated but never saved; uc_context_restore()
// refuses such contexts. Also gates the per-bit restore branches
// and the uc_context_reg_*() entry points.
uc_context_content context_content;
// Engine the memory state was captured against. Set only when
// UC_CTL_CONTEXT_MEMORY is included; checked on restore to refuse
// a memory-restore against a different uc_engine. CPU-only
// contexts leave this NULL and stay portable across engines with
// matching arch/mode.
struct uc_struct *engine;
char data[0]; // context
};

Expand Down
103 changes: 103 additions & 0 deletions tests/unit/test_ctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,103 @@ static void test_noexec(void)
OK(uc_close(uc));
}

// Regression test for #2319. uc_context_alloc() followed by
// uc_context_restore() (no save in between) used to abort the host
// process via the cpu_asidx_from_attrs() assertion in cpu.h.
static void test_uc_context_restore_without_save(void)
{
uc_engine *uc;
uc_context *ctx;
uint8_t code[] = {0x90}; // nop

OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc));
OK(uc_context_alloc(uc, &ctx));

uc_assert_err(UC_ERR_ARG, uc_context_restore(uc, ctx));

// reg_*() on a never-saved context is also refused, symmetric
// with restore.
{
int regid = UC_X86_REG_RAX;
uint64_t value = 0;
uc_assert_err(UC_ERR_ARG, uc_context_reg_read(ctx, regid, &value));
uc_assert_err(UC_ERR_ARG, uc_context_reg_write(ctx, regid, &value));
}

// The engine must still be usable.
OK(uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL));
OK(uc_mem_write(uc, 0x1000, code, sizeof(code)));
OK(uc_emu_start(uc, 0x1000, 0x1000 + sizeof(code), 0, 1));

// Saving then restoring works as before.
OK(uc_context_save(uc, ctx));
OK(uc_context_restore(uc, ctx));

OK(uc_context_free(ctx));
OK(uc_close(uc));
}

// Regression test for #2320. A context whose memory state was captured
// against engine A must not restore into engine B; the fv / last_block
// pointers it carries are live host pointers tied to A's address space.
// CPU-only contexts stay portable across engines with matching arch
// and mode.
static void test_uc_context_restore_cross_engine(void)
{
uc_engine *uc1, *uc2;
uc_context *ctx_cpu, *ctx_mem;

// CPU-only: cross-engine restore must succeed (no engine-specific
// state captured).
OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc1));
OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc2));
OK(uc_context_alloc(uc1, &ctx_cpu));
OK(uc_context_save(uc1, ctx_cpu));
OK(uc_context_restore(uc2, ctx_cpu));
OK(uc_context_free(ctx_cpu));

// Memory-mode: cross-engine restore must be refused.
OK(uc_ctl(uc1, UC_CTL_WRITE(UC_CTL_CONTEXT_MODE, 1),
UC_CTL_CONTEXT_CPU | UC_CTL_CONTEXT_MEMORY));
OK(uc_ctl(uc2, UC_CTL_WRITE(UC_CTL_CONTEXT_MODE, 1),
UC_CTL_CONTEXT_CPU | UC_CTL_CONTEXT_MEMORY));
OK(uc_mem_map(uc1, 0x1000, 0x1000, UC_PROT_ALL));
OK(uc_mem_map(uc2, 0x1000, 0x1000, UC_PROT_ALL));

OK(uc_context_alloc(uc1, &ctx_mem));
OK(uc_context_save(uc1, ctx_mem));
uc_assert_err(UC_ERR_ARG, uc_context_restore(uc2, ctx_mem));
OK(uc_context_restore(uc1, ctx_mem));

OK(uc_context_free(ctx_mem));
OK(uc_close(uc1));
OK(uc_close(uc2));
}

// uc_context_reg_*() must reject contexts that don't carry CPU state
// (memory-only snapshots), since their data buffer is empty.
static void test_uc_context_reg_rejects_memory_only(void)
{
uc_engine *uc;
uc_context *ctx;
int regid = UC_X86_REG_RAX;
uint64_t value = 0;

OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc));
OK(uc_ctl(uc, UC_CTL_WRITE(UC_CTL_CONTEXT_MODE, 1),
UC_CTL_CONTEXT_MEMORY));
OK(uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL));

OK(uc_context_alloc(uc, &ctx));
OK(uc_context_save(uc, ctx));

uc_assert_err(UC_ERR_ARG, uc_context_reg_read(ctx, regid, &value));
uc_assert_err(UC_ERR_ARG, uc_context_reg_write(ctx, regid, &value));

OK(uc_context_free(ctx));
OK(uc_close(uc));
}

TEST_LIST = {
{"test_uc_ctl_mode", test_uc_ctl_mode},
{"test_uc_ctl_page_size", test_uc_ctl_page_size},
Expand All @@ -416,4 +513,10 @@ TEST_LIST = {
{"test_uc_emu_stop_set_ip", test_uc_emu_stop_set_ip},
{"test_tlb_clear", test_tlb_clear},
{"test_noexec", test_noexec},
{"test_uc_context_restore_without_save",
test_uc_context_restore_without_save},
{"test_uc_context_restore_cross_engine",
test_uc_context_restore_cross_engine},
{"test_uc_context_reg_rejects_memory_only",
test_uc_context_reg_rejects_memory_only},
{NULL, NULL}};
93 changes: 89 additions & 4 deletions uc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2262,12 +2262,16 @@ uc_err uc_context_alloc(uc_engine *uc, uc_context **context)

UC_INIT(uc);

*_context = g_malloc(size);
// Zero-initialise the whole allocation so a stray uc_context_restore()
// before any uc_context_save() sees context_content == 0 and bails out
// with UC_ERR_ARG instead of feeding garbage snapshot_level / fv /
// last_block into the engine and tripping the cpu_asidx_from_attrs()
// assertion in cpu.h. Covers #2319.
*_context = g_malloc0(size);
if (*_context) {
(*_context)->context_size = size - sizeof(uc_context);
(*_context)->arch = uc->arch;
(*_context)->mode = uc->mode;
(*_context)->fv = NULL;
restore_jit_state(uc);
return UC_ERR_OK;
} else {
Expand Down Expand Up @@ -2303,6 +2307,16 @@ uc_err uc_context_save(uc_engine *uc, uc_context *context)
UC_INIT(uc);
uc_err ret = UC_ERR_OK;

// Tag the context with what we are about to save and which engine
// owns the memory state. uc_context_restore() refuses contexts whose
// context_content is 0 (never saved, #2319), arch/mode mismatches
// the destination engine, or whose engine pointer doesn't match on
// a memory restore (#2320). uc_context_reg_*() refuses memory-only
// snapshots via context_content as well.
context->context_content = uc->context_content;
context->engine =
(uc->context_content & UC_CTL_CONTEXT_MEMORY) ? uc : NULL;

if (uc->context_content & UC_CTL_CONTEXT_MEMORY) {
if (!context->fv) {
context->fv = g_malloc0(sizeof(*context->fv));
Expand Down Expand Up @@ -2458,9 +2472,25 @@ static context_reg_rw_t find_context_reg_rw(uc_arch arch, uc_mode mode)
return rw;
}

// Refuse register r/w unless the context advertises CPU state. This
// rejects both memory-only snapshots (whose data buffer holds no CPU
// state) and never-saved contexts (context_content == 0), which is
// symmetric with uc_context_restore() refusing context_content == 0.
static inline uc_err uc_context_check_cpu_state(const uc_context *ctx)
{
if (!(ctx->context_content & UC_CTL_CONTEXT_CPU)) {
return UC_ERR_ARG;
}
return UC_ERR_OK;
}

UNICORN_EXPORT
uc_err uc_context_reg_write(uc_context *ctx, int regid, const void *value)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
int setpc = 0;
size_t size = (size_t)-1;
return find_context_reg_rw(ctx->arch, ctx->mode)
Expand All @@ -2470,6 +2500,10 @@ uc_err uc_context_reg_write(uc_context *ctx, int regid, const void *value)
UNICORN_EXPORT
uc_err uc_context_reg_read(uc_context *ctx, int regid, void *value)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
size_t size = (size_t)-1;
return find_context_reg_rw(ctx->arch, ctx->mode)
.read(ctx->data, ctx->mode, regid, value, &size);
Expand All @@ -2479,6 +2513,10 @@ UNICORN_EXPORT
uc_err uc_context_reg_write2(uc_context *ctx, int regid, const void *value,
size_t *size)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
int setpc = 0;
return find_context_reg_rw(ctx->arch, ctx->mode)
.write(ctx->data, ctx->mode, regid, value, size, &setpc);
Expand All @@ -2488,6 +2526,10 @@ UNICORN_EXPORT
uc_err uc_context_reg_read2(uc_context *ctx, int regid, void *value,
size_t *size)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
return find_context_reg_rw(ctx->arch, ctx->mode)
.read(ctx->data, ctx->mode, regid, value, size);
}
Expand All @@ -2496,6 +2538,10 @@ UNICORN_EXPORT
uc_err uc_context_reg_write_batch(uc_context *ctx, int const *regs,
void *const *vals, int count)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
reg_write_t reg_write = find_context_reg_rw(ctx->arch, ctx->mode).write;
void *env = ctx->data;
int mode = ctx->mode;
Expand All @@ -2519,6 +2565,10 @@ UNICORN_EXPORT
uc_err uc_context_reg_read_batch(uc_context *ctx, int const *regs, void **vals,
int count)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
reg_read_t reg_read = find_context_reg_rw(ctx->arch, ctx->mode).read;
void *env = ctx->data;
int mode = ctx->mode;
Expand All @@ -2542,6 +2592,10 @@ uc_err uc_context_reg_write_batch2(uc_context *ctx, int const *regs,
const void *const *vals, size_t *sizes,
int count)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
reg_write_t reg_write = find_context_reg_rw(ctx->arch, ctx->mode).write;
void *env = ctx->data;
int mode = ctx->mode;
Expand All @@ -2564,6 +2618,10 @@ UNICORN_EXPORT
uc_err uc_context_reg_read_batch2(uc_context *ctx, int const *regs,
void *const *vals, size_t *sizes, int count)
{
uc_err err = uc_context_check_cpu_state(ctx);
if (err) {
return err;
}
reg_read_t reg_read = find_context_reg_rw(ctx->arch, ctx->mode).read;
void *env = ctx->data;
int mode = ctx->mode;
Expand All @@ -2587,7 +2645,32 @@ uc_err uc_context_restore(uc_engine *uc, uc_context *context)
UC_INIT(uc);
uc_err ret;

if (uc->context_content & UC_CTL_CONTEXT_MEMORY) {
// Refuse never-saved contexts (#2319) and ones whose recorded
// arch/mode does not match the destination engine (#2320). Both
// would otherwise propagate uninitialised or wrong-sized state into
// the engine and trip cpu_asidx_from_attrs() or read garbage host
// pointers out of fv / last_block.
if (context->context_content == 0 ||
context->arch != uc->arch ||
context->mode != uc->mode) {
restore_jit_state(uc);
return UC_ERR_ARG;
}

// Each restore branch is gated on both sides advertising the
// matching content bit. Restoring a CPU-only context onto an engine
// that asks for memory restore (or vice versa) is silently a no-op
// for the missing branch, matching the maintainer's intent of
// avoiding "restore a CPU context as a memory context".
if ((uc->context_content & UC_CTL_CONTEXT_MEMORY) &&
(context->context_content & UC_CTL_CONTEXT_MEMORY)) {
// The fv / last_block fields the memory restore is about to
// dereference are live host pointers that only make sense on
// the engine that captured them.
if (context->engine != uc) {
restore_jit_state(uc);
return UC_ERR_ARG;
}
uc->snapshot_level = context->snapshot_level;
if (!uc->flatview_copy(uc, uc->address_space_memory.current_map,
context->fv, true)) {
Expand All @@ -2604,7 +2687,8 @@ uc_err uc_context_restore(uc_engine *uc, uc_context *context)
uc->tcg_flush_tlb(uc);
}

if (uc->context_content & UC_CTL_CONTEXT_CPU) {
if ((uc->context_content & UC_CTL_CONTEXT_CPU) &&
(context->context_content & UC_CTL_CONTEXT_CPU)) {
if (!uc->context_restore) {
memcpy(uc->cpu->env_ptr, context->data, context->context_size);
restore_jit_state(uc);
Expand All @@ -2615,6 +2699,7 @@ uc_err uc_context_restore(uc_engine *uc, uc_context *context)
return ret;
}
}
restore_jit_state(uc);
return UC_ERR_OK;
}

Expand Down