From e95d8e915dd2edee81ea7ddf5a1a764ddcec623c Mon Sep 17 00:00:00 2001 From: Yang Liu Date: Sat, 18 Apr 2026 16:27:44 +0800 Subject: [PATCH] [DYNAREC] Fixed callret=2 with always_test out-of-bounds read With callret=2 and always_test=1, CALLRET_RET in pass2 skips writing callret entries, thus callret_size=0. But CALLRET_GETRET in pass3 still reads callrets[0], which is an out-of-bounds read. The garbage offset becomes the native return address, causing RET to jump to a wrong address. I had this issue on LoongArch with 16KiB page size kernel, but this seems universal so all three backends are changed. --- src/dynarec/arm64/dynarec_arm64_00.c | 8 ++++---- src/dynarec/la64/dynarec_la64_00.c | 8 ++++---- src/dynarec/la64/dynarec_la64_pass3.h | 4 ++-- src/dynarec/rv64/dynarec_rv64_00_3.c | 6 +++--- src/dynarec/rv64/dynarec_rv64_pass2.h | 4 ++-- src/dynarec/rv64/dynarec_rv64_pass3.h | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/dynarec/arm64/dynarec_arm64_00.c b/src/dynarec/arm64/dynarec_arm64_00.c index f98a5edf48..200cf20c65 100644 --- a/src/dynarec/arm64/dynarec_arm64_00.c +++ b/src/dynarec/arm64/dynarec_arm64_00.c @@ -3825,7 +3825,7 @@ uintptr_t dynarec64_00(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin // Push actual return address if(addr < (dyn->start+dyn->isize)) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts)?(dyn->insts[ninst].epilog-(dyn->native_size)):0; @@ -4533,7 +4533,7 @@ uintptr_t dynarec64_00(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin // Push actual return address if(addr < (dyn->start+dyn->isize)) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts)?(dyn->insts[ninst].epilog-(dyn->native_size)):0; @@ -4588,14 +4588,14 @@ uintptr_t dynarec64_00(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin // Push actual return address. Note that CS will not be tested, but that should be ok? if(can_continue) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts)?(dyn->insts[ninst].epilog-(dyn->native_size)):0; ADR_S20(x4, j64); MESSAGE(LOG_NONE, "\tCALLRET set return to +%di\n", j64>>2); } else { - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts)?(GETMARK-(dyn->native_size)):0; diff --git a/src/dynarec/la64/dynarec_la64_00.c b/src/dynarec/la64/dynarec_la64_00.c index 42bd440312..a483fcf9c3 100644 --- a/src/dynarec/la64/dynarec_la64_00.c +++ b/src/dynarec/la64/dynarec_la64_00.c @@ -3382,7 +3382,7 @@ uintptr_t dynarec64_00(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni // Push actual return address if (addr < (dyn->start + dyn->isize)) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts) ? (dyn->insts[ninst].epilog - (dyn->native_size)) : 0; @@ -4023,7 +4023,7 @@ uintptr_t dynarec64_00(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni // Push actual return address if (addr < (dyn->start + dyn->isize)) { // there is a next - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts) ? (dyn->insts[ninst].epilog - (dyn->native_size)) : 0; @@ -4082,7 +4082,7 @@ uintptr_t dynarec64_00(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni // Push actual return address. Note that CS will not be tested, but that should be ok? if (can_continue) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts) ? (dyn->insts[ninst].epilog - (dyn->native_size)) : 0; @@ -4090,7 +4090,7 @@ uintptr_t dynarec64_00(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni ADDI_D(x4, x4, j64 & 0xfff); MESSAGE(LOG_NONE, "\tCALLRET set return to +%di\n", j64 >> 2); } else { - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts) ? (GETMARK - (dyn->native_size)) : 0; diff --git a/src/dynarec/la64/dynarec_la64_pass3.h b/src/dynarec/la64/dynarec_la64_pass3.h index e2a15b3235..39b9951a0d 100644 --- a/src/dynarec/la64/dynarec_la64_pass3.h +++ b/src/dynarec/la64/dynarec_la64_pass3.h @@ -65,7 +65,7 @@ } while (0) #define CALLRET_RET(A) \ do { \ - if((A) && ISSEP() && BOX64DRENV(dynarec_callret) && !dyn->always_test) {\ + if((A) && ISSEP() && BOX64DRENV(dynarec_callret)) { \ MESSAGE(LOG_DUMP, " Dynablock*\n"); \ dyn->block += sizeof(void*); \ dyn->native_size+=sizeof(void*); \ @@ -74,7 +74,7 @@ dyn->sep[dyn->sep_size].nat_offs = dyn->native_size; \ ++dyn->sep_size; \ } \ - if((A) && (BOX64DRENV(dynarec_callret)>1)) { \ + if((A) && (BOX64DRENV(dynarec_callret)>1) && !dyn->always_test) { \ dyn->callrets[dyn->callret_size].type = 0; \ dyn->callrets[dyn->callret_size++].offs = dyn->native_size; \ EMIT(ARCH_NOP); \ diff --git a/src/dynarec/rv64/dynarec_rv64_00_3.c b/src/dynarec/rv64/dynarec_rv64_00_3.c index 9c11080da8..23a200c77a 100644 --- a/src/dynarec/rv64/dynarec_rv64_00_3.c +++ b/src/dynarec/rv64/dynarec_rv64_00_3.c @@ -1147,7 +1147,7 @@ uintptr_t dynarec64_00_3(dynarec_rv64_t* dyn, uintptr_t addr, uintptr_t ip, int // Push actual return address if(can_continue) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts)?(GETMARK-(dyn->native_size)):0; @@ -1714,7 +1714,7 @@ uintptr_t dynarec64_00_3(dynarec_rv64_t* dyn, uintptr_t addr, uintptr_t ip, int // Push actual return address if(addr < (dyn->start+dyn->isize)) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts)?(GETMARK-(dyn->native_size)):0; @@ -1777,7 +1777,7 @@ uintptr_t dynarec64_00_3(dynarec_rv64_t* dyn, uintptr_t addr, uintptr_t ip, int // Push actual return address if(addr < (dyn->start+dyn->isize)) { // there is a next... - if(BOX64DRENV(dynarec_callret)>1) + if(BOX64DRENV(dynarec_callret)>1 && !dyn->always_test) j64 = CALLRET_GETRET(); else j64 = (dyn->insts)?(GETMARK-(dyn->native_size)):0; diff --git a/src/dynarec/rv64/dynarec_rv64_pass2.h b/src/dynarec/rv64/dynarec_rv64_pass2.h index 856970eadd..b48db1241b 100644 --- a/src/dynarec/rv64/dynarec_rv64_pass2.h +++ b/src/dynarec/rv64/dynarec_rv64_pass2.h @@ -55,11 +55,11 @@ EMIT(0); \ EMIT(0); \ } while (0) -#define CALLRET_RET(A) do { \ +#define CALLRET_RET(A) do { \ if((A) && ISSEP() && BOX64DRENV(dynarec_callret)) { \ dyn->insts[ninst].size+=sizeof(void*); dyn->native_size+=sizeof(void*); dyn->insts[ninst+1].sep=1; ++dyn->sep_size; \ } \ - if((A) && (BOX64DRENV(dynarec_callret)>1)) { \ + if((A) && (BOX64DRENV(dynarec_callret)>1) && !dyn->always_test) { \ dyn->callrets[dyn->callret_size].type = 0; dyn->callrets[dyn->callret_size++].offs = dyn->native_size; EMIT(ARCH_NOP); \ } \ } while(0) diff --git a/src/dynarec/rv64/dynarec_rv64_pass3.h b/src/dynarec/rv64/dynarec_rv64_pass3.h index 2afd08bd87..db843bbdb5 100644 --- a/src/dynarec/rv64/dynarec_rv64_pass3.h +++ b/src/dynarec/rv64/dynarec_rv64_pass3.h @@ -80,7 +80,7 @@ return 0 #define CALLRET_RET(A) \ do { \ - if((A) && ISSEP() && BOX64DRENV(dynarec_callret) && !dyn->always_test) {\ + if((A) && ISSEP() && BOX64DRENV(dynarec_callret)) {\ MESSAGE(LOG_DUMP, " Dynablock*\n"); \ dyn->block += sizeof(void*); \ dyn->native_size+=sizeof(void*); \ @@ -89,7 +89,7 @@ dyn->sep[dyn->sep_size].nat_offs = dyn->native_size; \ ++dyn->sep_size; \ } \ - if((A) && (BOX64DRENV(dynarec_callret)>1)) { \ + if((A) && (BOX64DRENV(dynarec_callret)>1) && !dyn->always_test) { \ dyn->callrets[dyn->callret_size].type = 0; \ dyn->callrets[dyn->callret_size++].offs = dyn->native_size; \ EMIT(ARCH_NOP); \