runtime: validate stale-static ranges in full — drop the exec_pc clip

dirty_ram_text_native_ok_ranges_from clipped validation to
[exec_pc, end), assuming a continuation never fetches bytes behind its
resume PC. False for any entry with a backward edge: a post-call
continuation re-enters mid-function and loops back into the clipped
region, executing stale static code. CMR2's overlay engine carries a
second copy of the vertex-transform kernel at 0x80016B34 that forked
exactly this way (r16 +0xA0 static vs +0x100 live, the same 0x60
delta as the first locus) after the inter-piece transfer guards
closed the fallthrough edge.

Validation now covers every emitted range of the entry regardless of
resume PC. A genuinely patched prologue blocks the whole entry to the
dirty-RAM interpreter -- correct, merely slower.
This commit is contained in:
Alexandros Mandravillis
2026-08-30 17:26:38 +03:00
parent 4d9060a54e
commit f07f801944
+9 -10
View File
@@ -482,15 +482,19 @@ int dirty_ram_text_native_ok(uint32_t phys) {
* a mismatch never poisons an unrelated 4 KB page forever: every decision is
* made from the live bytes the native body will actually execute.
*
* exec_pc is the dispatch/resume address. Ranges that end at or before that PC
* are skipped, and a range that straddles it is clipped to [exec_pc, end). A
* runtime patch of a function prologue must not block a compiled continuation
* that never fetches the patched bytes. */
* exec_pc is the dispatch/resume address, kept for observability and future
* use; ranges are validated IN FULL regardless of it. The former
* [exec_pc, end) clip assumed a continuation "never fetches the patched
* bytes" behind its resume PC — false for any entry with a backward edge:
* a post-call continuation re-enters mid-function and loops back into the
* clipped-away region, executing stale static code (CMR2 overlay
* corruption, second locus 0x80016B34). A genuinely patched prologue now
* blocks the whole entry to the interpreter — correct, merely slower. */
int dirty_ram_text_native_ok_ranges_from(const uint32_t *lo_len_pairs,
uint32_t count,
uint32_t exec_pc) {
if (!text_ref_image || !lo_len_pairs || count == 0) return 0;
uint32_t at = exec_pc & 0x1FFFFFFFu;
(void)exec_pc;
int any = 0;
for (uint32_t i = 0; i < count; i++) {
uint32_t phys = lo_len_pairs[i * 2u] & 0x1FFFFFFFu;
@@ -500,11 +504,6 @@ int dirty_ram_text_native_ok_ranges_from(const uint32_t *lo_len_pairs,
g_text_native_blocked++;
return 0;
}
if (phys + len <= at) continue;
if (phys < at) {
len -= at - phys;
phys = at;
}
any = 1;
/* Page-clean fast path: every page this range touches is neither
* guard-modified nor runtime-dirty, so its bytes still equal the