Skip to content

uc_context: validate save/restore against content flags and engine#2322

Open
MarkLee131 wants to merge 2 commits into
unicorn-engine:devfrom
MarkLee131:fix/issue-2319-context-alloc-zero-init
Open

uc_context: validate save/restore against content flags and engine#2322
MarkLee131 wants to merge 2 commits into
unicorn-engine:devfrom
MarkLee131:fix/issue-2319-context-alloc-zero-init

Conversation

@MarkLee131
Copy link
Copy Markdown

@MarkLee131 MarkLee131 commented Apr 25, 2026

Two failure modes in uc_context_save / uc_context_restore could abort or corrupt the host process.

  1. uc_context_alloc() returned a heap allocation with only fv explicitly zeroed; snapshot_level / ramblock_freed / last_block stayed garbage. A stray uc_context_restore() before any uc_context_save() propagated those
    into the engine and tripped the cpu_asidx_from_attrs() assertion in cpu.h on the next emu_start().

  2. uc_context_restore() trusted fv / last_block from the user-visible struct without checking they came from a uc_context_save() on the same engine. Cross-engine restores dereferenced live host pointers belonging to a
    different uc_engine.

Changes:

Approach follows the review feedback on this PR and on #2320.

Regression tests in tests/unit/test_ctl.c:

  • restore without save returns UC_ERR_ARG, engine still usable
  • cross-engine restore: CPU-only context portable, memory-mode refused
  • uc_context_reg_read / _reg_write on memory-only snapshot returns UC_ERR_ARG

Closes part of #1766. Fixes #2319. Fixes #2320. Supersedes #2325.

@PhilippTakacs
Copy link
Copy Markdown
Contributor

I would prefer to save uc->context_content in the context itself. This can also be used as a check in the uc_context_reg_* function to prevent register read/write on memory-only snapshots.

Two failure modes used to abort or corrupt the host process:

  1. uc_context_alloc() returned a heap allocation with only fv
     explicitly zeroed; snapshot_level / ramblock_freed / last_block
     stayed garbage. uc_context_restore() then trusted those fields
     and tripped the cpu_asidx_from_attrs() assertion in cpu.h on
     the next emu_start().

  2. uc_context_restore() trusted fv / last_block from the
     user-visible struct without checking they came from a
     uc_context_save() on the same engine; cross-engine restores
     dereferenced live host pointers belonging to a different
     uc_engine.

Three changes here:

  - uc_context_alloc() switches to g_malloc0() so untouched header
    fields read 0.

  - struct uc_context grows a context_content field set by
    uc_context_save() to mirror uc->context_content, and an engine
    pointer set only when UC_CTL_CONTEXT_MEMORY is included.
    uc_context_restore() refuses contexts with context_content == 0
    (covers unicorn-engine#2319), with arch/mode that doesn't match the destination
    engine, or with an engine pointer mismatch on a memory restore
    (covers unicorn-engine#2320). Each restore branch is gated on both sides
    advertising the matching content bit, so a CPU-only context
    can't be replayed as a memory snapshot.

  - uc_context_reg_*() refuses memory-only contexts via
    context_content, since their data buffer holds no CPU state.

Adds three regression tests in tests/unit/test_ctl.c:
restore-without-save, cross-engine restore (CPU-only portable,
memory-mode rejected), and reg-read/write on a memory-only snapshot.

Closes part of unicorn-engine#1766. Fixes unicorn-engine#2319. Fixes unicorn-engine#2320.
@MarkLee131 MarkLee131 force-pushed the fix/issue-2319-context-alloc-zero-init branch from db67d17 to b1badf9 Compare April 29, 2026 12:08
@MarkLee131 MarkLee131 changed the title uc_context: reject restore from a never-saved context uc_context: validate save/restore against content flags and engine Apr 29, 2026
@MarkLee131 MarkLee131 changed the base branch from master to dev April 29, 2026 12:11
@MarkLee131
Copy link
Copy Markdown
Author

rewrote on dev. struct uc_context now carries context_content, engine (set only when MEMORY) and arch/mode; restore checks all four, and reg_*() refuses memory-only ctx as you suggested. closed #2325, this PR covers both #2319 and #2320 now.

Comment thread uc.c Outdated
Comment on lines +2475 to +2484
// Refuse register r/w on memory-only contexts: their data buffer
// holds no CPU state, so the call would just shuffle zeros into the
// caller's value (read) or be lost on the next restore (write).
// Untouched contexts (context_content == 0) are left alone, so the
// uc_context_alloc + uc_context_reg_write + uc_context_save pattern
// keeps working.
static inline uc_err uc_context_check_cpu_state(const uc_context *ctx)
{
if ((ctx->context_content & UC_CTL_CONTEXT_MEMORY) &&
!(ctx->context_content & UC_CTL_CONTEXT_CPU)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you want to achieve with this. When we refuse to restore a context which was never saved writing to a register of the context is doing nothing. So why allow this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carve-out was for alloc + reg_write + save, but uc_context_save overwrites the buffer from the engine so the reg_write gets clobbered anyway. Tightened to require UC_CTL_CONTEXT_CPU, symmetric with restore. Test extended.

Previously the check only refused contexts with MEMORY set and CPU
unset; never-saved contexts (context_content == 0) were left alone
on the assumption that an alloc + reg_write + save pattern might
exist. uc_context_save() overwrites the data buffer from the engine
though, so any reg_write before save would be clobbered, and there
is no real caller for that path. Reject anything without
UC_CTL_CONTEXT_CPU set, symmetric with uc_context_restore() refusing
context_content == 0.

Extends the unicorn-engine#2319 regression test to assert uc_context_reg_read /
uc_context_reg_write also reject a never-saved context.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

uc_context_alloc leaves header fields uninitialized; misuse aborts the host

2 participants