fix(cpus): correct CVE-2025-11689/0647 combined trap handler on N2 The combined WORKAROUND_CVE_2025_0647 && CVE_2025_11689_DYNAMIC_WA path in neoverse_n2_impl_defined_el3_handler had several porting bugs that caused VM hangs and register corruption on Neoverse N2 (CN103/CN105): - bl->b: handler used bl to tail-call workaround functions, clobbering x30 and causing execution to fall into neoverse_n2_reset_func. - SP misuse: wa_cve_2025_0647_do_cpp_wa requires SP==CTX_BASE but was called from C, where SP is offset by the C frame. Refactored handle_tcr_or_cpp_exception to return 0/1 so the assembly dispatcher can tail-call it directly with SP restored to CTX_BASE. - Register save: ea_delegate.S only saved x0-x5; the C handler clobbered x6-x18, corrupting guest registers on ERET. Extended save/restore to cover x0-x29 for the CVE_2025_11689_DYNAMIC_WA fast path. Signed-off-by: Suman Ghosh <sumang@marvell.com>
diff --git a/bl31/aarch64/ea_delegate.S b/bl31/aarch64/ea_delegate.S index 835ba7f..1b74504 100644 --- a/bl31/aarch64/ea_delegate.S +++ b/bl31/aarch64/ea_delegate.S
@@ -67,6 +67,27 @@ stp x0, x1, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X0] stp x2, x3, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X2] stp x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4] +#if CVE_2025_11689_DYNAMIC_WA + /* + * The CVE-2025-11689 IMP_DEF handler calls C code (including printf + * for debug) which clobbers x6-x18. x19-x29 are callee-saved by C + * but the assembly dispatch in neoverse_n2_impl_defined_el3_handler + * may also clobber them. Save the full set here so that ea_delegate + * and the cpp_wa tail-call path can restore all victim registers. + */ + stp x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6] + stp x8, x9, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8] + stp x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10] + stp x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12] + stp x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14] + stp x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16] + stp x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18] + stp x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20] + stp x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22] + stp x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24] + stp x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26] + stp x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28] +#endif /* CVE_2025_11689_DYNAMIC_WA */ /* Get the cpu_ops pointer */ bl get_cpu_ops_ptr @@ -95,6 +116,19 @@ add x16, x16, #4 msr elr_el3, x16 isb + /* Restore all victim GP registers (x6-x29 were saved on entry). */ + ldp x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6] + ldp x8, x9, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8] + ldp x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10] + ldp x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12] + ldp x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14] + ldp x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16] + ldp x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18] + ldp x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20] + ldp x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22] + ldp x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24] + ldp x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26] + ldp x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28] ldp x0, x1, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X0] ldp x2, x3, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X2] ldp x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4]
diff --git a/include/lib/cpus/aarch64/cve_2025_11689.h b/include/lib/cpus/aarch64/cve_2025_11689.h index 3050e39..8bb6678 100644 --- a/include/lib/cpus/aarch64/cve_2025_11689.h +++ b/include/lib/cpus/aarch64/cve_2025_11689.h
@@ -26,7 +26,7 @@ } cve_2025_11689_dynamic_wa_mode_e; void cve_2025_11689_imp_def_trap_workaround(void); -void handle_tcr_or_cpp_exception(uint64_t flags); +int handle_tcr_or_cpp_exception(uint64_t flags); void write_actlr2_el1_bit54(uint64_t set); void wa_cve_2025_11689_power_on_finish(void); void wa_cve_2025_11689_suspend_finish(void);
diff --git a/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c b/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c index 0f91e61..b8a3408 100644 --- a/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c +++ b/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c
@@ -185,22 +185,33 @@ } #if WORKAROUND_CVE_2025_0647 -void handle_tcr_or_cpp_exception(uint64_t flags) +/* + * Returns 0 if the TCR/VTCR workaround was applied (caller should ERET via + * ea_delegate.S after advancing ELR_EL3 by 4). + * Returns 1 if the CPP workaround is needed (caller must tail-call + * wa_cve_2025_0647_do_cpp_wa with SP == CTX_BASE; calling it from C would + * corrupt register restore since it uses SP-relative ctx loads). + */ +int handle_tcr_or_cpp_exception(uint64_t flags) { uint32_t pos = plat_my_core_pos(); + uint8_t src_el = + (uint8_t)((read_spsr_el3() >> SPSR_EL_SHIFT) & SPSR_EL_MASK); - if (((read_spsr_el3() >> SPSR_EL_SHIFT) & SPSR_EL_MASK) == 0U) { - wa_cve_2025_0647_do_cpp_wa(flags | WA_IS_TRAP_HANDLER); - return; + (void)flags; + + if (src_el == 0U) { + return 1; /* cpp_wa for EL0 */ } if (shadow_tcr_el1[pos] != read_tcr_el1() || shadow_vtcr_el2[pos] != read_vtcr_el2()) { cve_2025_11689_imp_def_trap_workaround(); - return; + return 0; /* tcr_wa handled; ea_delegate.S advances ELR+4 + ERET */ } - wa_cve_2025_0647_do_cpp_wa(flags | WA_IS_TRAP_HANDLER); + /* cpp_wa for EL1/EL2: caller must tail-call wa_cve_2025_0647_do_cpp_wa */ + return 1; } #endif /* WORKAROUND_CVE_2025_0647 */
diff --git a/lib/cpus/aarch64/neoverse_n2.S b/lib/cpus/aarch64/neoverse_n2.S index fdd90e9..9d6a9ab 100644 --- a/lib/cpus/aarch64/neoverse_n2.S +++ b/lib/cpus/aarch64/neoverse_n2.S
@@ -607,8 +607,47 @@ func neoverse_n2_impl_defined_el3_handler #if IMAGE_BL31 #if WORKAROUND_CVE_2025_0647 && CVE_2025_11689_DYNAMIC_WA + /* + * Save the ea_delegate.S return address to a stack slot below the + * cpu_context. The C function modifies SP (pushing its own frame), so + * we cannot use a callee-saved register without clobbering the victim's + * value. Reserve 16 bytes (alignment required); save x30 at [sp+8]. + * The C function starts pushing at sp-16 (below our slot), so our + * saved value is safe. After bl returns, SP is restored by C to + * ctx_base-16; we then ldr+add to get back to ctx_base before any + * tail-call that needs SP == ctx_base. + */ + sub sp, sp, #16 + str x30, [sp, #8] mov x0, #WA_LS_RCG_EN bl handle_tcr_or_cpp_exception + /* x0 == 0: tcr_wa handled; ea_delegate.S will advance ELR+4 + ERET */ + /* x0 != 0: cpp_wa needed; tail-call with SP restored to ctx_base */ + cbz x0, 1f + ldr x30, [sp, #8] + add sp, sp, #16 + /* Restore x6-x29: C handler clobbered caller-saved regs; x6-x29 were + * saved to the cpu_context by ea_delegate.S on exception entry. + * wa_cve_2025_0647_do_cpp_wa restores x0-x5 and x30 itself. + * storing/restoring x6–x18 would be sufficient from a pure C ABI + * standpoint. But covering x6-x29 ensures no values are overwritten. */ + ldp x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6] + ldp x8, x9, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8] + ldp x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10] + ldp x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12] + ldp x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14] + ldp x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16] + ldp x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18] + ldp x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20] + ldp x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22] + ldp x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24] + ldp x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26] + ldp x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28] + mov x0, #(WA_LS_RCG_EN | WA_IS_TRAP_HANDLER) + b wa_cve_2025_0647_do_cpp_wa +1: ldr x30, [sp, #8] + add sp, sp, #16 + ret #elif WORKAROUND_CVE_2025_0647 mov x0, #WA_LS_RCG_EN @@ -618,7 +657,7 @@ orr x0, x0, #WA_IS_TRAP_HANDLER b wa_cve_2025_0647_do_cpp_wa #elif CVE_2025_11689_DYNAMIC_WA - bl cve_2025_11689_imp_def_trap_workaround + b cve_2025_11689_imp_def_trap_workaround #endif #endif endfunc neoverse_n2_impl_defined_el3_handler