[PATCH v2] x86/alternative: WARN and skip unrecognized indirect ca= only for non-O2 builds
From: Zeliang Li
Date: Tue Aug 11 2026 - 07:09:09 EST
When the kernel is compiled at a non-default optimization level
(e.g., KCFLAGS=3D-O1 or -O0), the compiler may emit call instruction
patterns that differ from the expected 6-byte sequence
(opcodes 0xff 0x15 followed by a 32-bit displacement) checked
by alt_replace_call().
Currently this triggers a BUG() in apply_alternatives(), causing
an immediate kernel panic during early boot.
For official -O2 builds, the BUG() is appropriate and should be
retained, as the kernel only guarantees correct operation at this
optimization level[reference:0].
For non-standard optimization levels used for debugging purposes,
crashing the kernel is too severe. Instead, issue a WARN_ONCE()
and skip patching for this particular site, preserving the original
indirect call.
This approach balances safety for production builds with usability
for developers using non-standard optimization levels.
Signed-off-by: Zeliang Li <zeliang.li@linux@xxxxxxxxx>
---
arch/x86/kernel/alternative.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 62936a3bde19..b8c9d0e1f2a3 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -554,8 +554,21 @@ static unsigned int alt_replace_call(u8 *instr,
u8 *insn_buff, struct alt_instr
if (a->instrlen !=3D 6 ||
instr[0] !=3D CALL_RIP_REL_OPCODE ||
instr[1] !=3D CALL_RIP_REL_MODRM) {
- pr_err("ALT_FLAG_DIRECT_CALL set for unrecognized indirect call\n");
- BUG();
+ /*
+ * Unrecognized indirect call pattern. For -O2 builds this is a
+ * fatal error - the kernel only guarantees correct operation at
+ * this optimization level. For non-O2 builds (debugging), skip
+ * patching to avoid a boot-time crash.
+ */
+#ifdef __OPTIMIZE__
+#if __OPTIMIZE__ =3D=3D 2
+ BUG();
+#endif
+#endif
+ WARN_ONCE(1,
+ "ALT_FLAG_DIRECT_CALL: unrecognized indirect call at %pS (instrlen=3D%d)=
\n",
+ instr, a->instrlen);
+ memcpy(insn_buff, instr, a->instrlen);
+ return a->instrlen;
}
J=C3=BCrgen Gro=C3=9F <jgross@xxxxxxxx> =E4=BA=8E2026=E5=B9=B48=E6=9C=8811=
=E6=97=A5=E5=91=A8=E4=BA=8C 19:50=E5=86=99=E9=81=93=EF=BC=9A
>
> On 11.08.26 13:41, =E6=9D=8E=E5=88=99=E8=89=AF wrote:
> > From cd58bd112a6178593c6bc9c0f9b307d6897ae3d0 Mon Sep 17 00:00:00 2001
> > From: Zeliang Li <lizeliang.linux@xxxxxxxxx <mailto:lizeliang.linux@gma=
il.com>>
> > Date: Tue, 11 Aug 2026 19:09:09 +0800
> > Subject: [PATCH] x86/alternatives: gracefully skip unrecognized indirec=
t call
> > instructions
> >
> > When the kernel is compiled at a non-default optimization level
> > (e.g., KCFLAGS=3D-O1), the compiler may emit call instruction
> > patterns that differ from the expected 6-byte sequence
> > (opcodes 0xff 0x15 followed by a 32-bit displacement) checked
> > by alt_replace_call().
> >
> > Currently this triggers a BUG() in apply_alternatives(), causing
> > an immediate kernel panic during early boot:
> >
> > kernel BUG at arch/x86/kernel/alternative.c:558!
> >
> > Instead of crashing, print a warning (once per boot) and preserve
> > the original call instruction verbatim. The call will execute as
> > compiled, without any alternatives patching applied. This is a
> > graceful degradation: the indirect call remains indirect, which is
> > functionally correct albeit slower than the direct-call patching
> > that was skipped.
> >
> > No change to normal -O2 builds, where call instructions continue
> > to match the expected pattern.
> >
> > Signed-off-by: Zeliang Li <lizeliang.linux@xxxxxxxxx
> > <mailto:lizeliang.linux@xxxxxxxxx>>
>
> Please don't send patches as HTML mails!
My apologies. I have now configured my Gmail to send plain text by default=
.
Thank you for pointing this out.
>
> > ---
> > arch/x86/kernel/alternative.c | 11 +++++++++--
> > 1 file changed, 9 insertions(+), 2 deletions(-)
> >
> > diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternativ=
e.c
> > index 62936a3bde19..0e3903e76b55 100644
> > --- a/arch/x86/kernel/alternative.c
> > +++ b/arch/x86/kernel/alternative.c
> > @@ -554,8 +554,15 @@ static unsigned int alt_replace_call(u8 *instr, u8
> > *insn_buff, struct alt_instr
> > if (a->instrlen !=3D 6 ||
> > instr[0] !=3D CALL_RIP_REL_OPCODE ||
> > instr[1] !=3D CALL_RIP_REL_MODRM) {
> > - pr_err("ALT_FLAG_DIRECT_CALL set for unrecognized indirect call\n");
> > - BUG();
> > + static bool warned;
> > +
> > + if (!warned) {
> > + warned =3D true;
> > + pr_warn("%s: skipping unrecognized indirect call (instrlen=3D%d)\n",
> > + __func__, a->instrlen);
> > + }
>
> You are open coding pr_warn_once() here.
>
> > + memcpy(insn_buff, instr, a->instrlen);
> > + return a->instrlen;
>
> And now you are letting an indirect call survive which is not subject to
> any cpu bug mitigations.
You are absolutely right =E2=80=93 this was a serious oversight in my origi=
nal
patch. Simply preserving the indirect call would bypass retpoline and
other CPU mitigations, which is unacceptable for production kernels.
To address this, I have revised the patch to distinguish between
optimization levels:
- For -O2 builds (the officially supported configuration), the existing
BUG() is retained, as the kernel only guarantees correct operation at
this level.
- For non-standard -O1/-O0 builds (typically used for debugging), we
issue a WARN_ONCE() and skip patching. This avoids a boot-time crash
for developers, while keeping the production (-O2) path fully secure.
This way, the security impact is limited to non-standard builds, which
are outside the official support scope and are already considered
"developer=E2=80=91only".
>
>
> Juergen
>
--=20
KISS =3D=3D Keep it simple,stupid~:-)
http://lizeliang.org