[PATCH v3 4/5] x86/alternative: Relocate all insn-relative fields

Andrew Cooper <[email protected]> Mon, 3 Aug 2026 08:20:05 +0100
Newsgroups org.xenproject.lists.xen-devel
Message-ID <[email protected]>
Right now, relocation of displacements is restricted to finding 0xe8/e9 as the
first byte of the replacement, but this is overly restrictive.

Use x86_decode_lite() to find and adjust all insn-relative fields.

As with disp8's not leaving the replacemnet block, some disp32's don't either.
e.g. the RSB stuffing loop.  These stay unmodified.

Signed-off-by: Andrew Cooper <[email protected]>
Reviewed-by: Jan Beulich <[email protected]>
---
CC: Jan Beulich <[email protected]>
CC: Roger Pau MonnĂ© <[email protected]>
CC: Teddy Astie <[email protected]>

v3:
 * Rebase over the split-out of altcall.  Substantially simpler.
---
 xen/arch/x86/alternative.c | 50 +++++++++++++++++++++++++++++++-------
 1 file changed, 41 insertions(+), 9 deletions(-)

diff --git a/xen/arch/x86/alternative.c b/xen/arch/x86/alternative.c
index fd03147bdd12..a4a65597b2fc 100644
--- a/xen/arch/x86/alternative.c
+++ b/xen/arch/x86/alternative.c
@@ -349,15 +349,47 @@ static int init_or_livepatch _apply_alternatives(struct alt_instr *start,
 
         memcpy(buf, repl, a->repl_len);
 
-        /* 0xe8/0xe9 are relative branches; fix the offset. */
-        if ( a->repl_len >= 5 && (*buf & 0xfe) == 0xe8 )
-            *(int32_t *)(buf + 1) += repl - orig;
-        else if ( IS_ENABLED(CONFIG_RETURN_THUNK) &&
-                  a->repl_len > 5 && buf[a->repl_len - 5] == 0xe9 &&
-                  ((long)repl + a->repl_len +
-                   *(int32_t *)(buf + a->repl_len - 4) ==
-                   (long)__x86_return_thunk) )
-            *(int32_t *)(buf + a->repl_len - 4) += repl - orig;
+        /*
+         * Walk buf[] and adjust any insn-relative operands which leave the
+         * replacement block.
+         */
+        if ( a->repl_len )
+        {
+            uint8_t *ip = buf, *repl_end = ip + a->repl_len;
+
+            for ( x86_decode_lite_t res; ip < repl_end; ip += res.len )
+            {
+                int32_t *d32;
+                const uint8_t *target;
+
+                res = x86_decode_lite(ip, repl_end);
+
+                if ( res.len == 0 )
+                {
+                    printk("Alt for %ps [%*ph]\n"
+                           "  Unable to decode instruction at +%lu in alternative\n",
+                           ALT_ORIG_PTR(a), a->repl_len, repl, ip - repl);
+                    return -EINVAL;
+                }
+
+                if ( res.rel_sz != 4 )
+                    continue;
+
+                d32 = res.rel;
+                target = ip + res.len + *d32;
+
+                if ( target >= buf && target <= repl_end )
+                {
+                    /*
+                     * Target doesn't leave the replacement block.  e.g. RSB
+                     * stuffing.  Leave it unmodified.
+                     */
+                    continue;
+                }
+
+                *d32 += repl - orig;
+            }
+        }
 
         a->priv = 1;
 
-- 
2.39.5