Re: [PATCH] x86/cfi: Fix FineIBT hash offset in cfi_get_func_hash()
From: Soheil Hassas Yeganeh
Date: Tue Sep 08 2026 - 11:14:20 EST
On Mon, Aug 31, 2026 at 10:49 AM Soheil Hassas Yeganeh
<soheil.kdev@xxxxxxxxx> wrote:
>
> The switch of the FineIBT preamble from "subl $hash, %r10d" to the
> shorter "subl $hash, %eax" moved the hash immediate from offset 7 to
> offset 5 of the preamble. fineibt_preamble_hash was updated to match,
> but the open-coded offset in cfi_get_func_hash() was missed and it
> still reads the hash at offset 7.
>
> cfi_get_func_hash() is used by the BPF JIT to give a struct_ops
> trampoline the CFI hash of the stub function it stands in for. With
> FineIBT the trampoline now gets the upper half of the real hash
> followed by the first two bytes of the next instruction, so the first
> indirect call from the kernel into a struct_ops program,
> tcp_init_congestion_control() calling ->init() of a BPF congestion
> control for example, fails the FineIBT check and the kernel dies with
> a CFI failure.
>
> Move the FineIBT preamble template and its offset defines above
> cfi_get_func_hash() and use fineibt_preamble_hash there, so every
> reader of the preamble shares one definition of its layout. The
> CFI_FINEIBT arm is only built with CONFIG_FINEIBT, the only
> configuration in which cfi_mode can take that value.
> cfi_get_func_arity() does not need the same treatment: the __bhi_args
> call whose displacement it reads still ends at the function address.
>
> Fixes: 85a2d4a890dc ("x86,ibt: Use UDB instead of 0xEA")
Gentle ping on this one. To recap: since 85a2d4a890dc the FineIBT preamble
stores the hash at +5, but cfi_get_func_hash() still reads it at +7, so BPF
struct_ops trampolines are emitted with a wrong hash and the first indirect call
through them (e.g. a bpf tcp_congestion_ops .cong_control) hits a FineIBT #CP.
We have large scale panics when we use BPF congestion control.
Happy to respin if a different shape is preferred (e.g. moving the
offset next to
the preamble definition), but this seemed like the best way to fix the issue.
Thanks,
Soheil
> Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
> Cc: Alexei Starovoitov <ast@xxxxxxxxxx>
> Cc: Daniel Borkmann <daniel@xxxxxxxxxxxxx>
> Cc: stable@xxxxxxxxxxxxxxx # 6.18+
> Assisted-by: LLM
> Signed-off-by: Soheil Hassas Yeganeh <soheil.kdev@xxxxxxxxx>
> ---
> arch/x86/kernel/alternative.c | 72 +++++++++++++++++++++++--------------------
> 1 file changed, 38 insertions(+), 34 deletions(-)
>
> diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
> index 91b1cdd16569..1b22062cabfe 100644
> --- a/arch/x86/kernel/alternative.c
> +++ b/arch/x86/kernel/alternative.c
> @@ -1198,6 +1198,41 @@ static bool cfi_debug __ro_after_init;
> bool cfi_bhi __ro_after_init = false;
> #endif
>
> +#ifdef CONFIG_FINEIBT
> +/*
> + * <fineibt_preamble_start>:
> + * 0: f3 0f 1e fa endbr64
> + * 4: 2d 78 56 34 12 sub $0x12345678, %eax
> + * 9: 2e 0f 85 03 00 00 00 jne,pn 13 <fineibt_preamble_start+0x13>
> + * 10: 0f 1f 40 d6 nopl -0x2a(%rax)
> + *
> + * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as
> + * UDB on x86_64 and raises #UD.
> + */
> +asm( ".pushsection .rodata \n"
> + "fineibt_preamble_start: \n"
> + " endbr64 \n"
> + " subl $0x12345678, %eax \n"
> + "fineibt_preamble_bhi: \n"
> + " cs jne.d32 fineibt_preamble_start+0x13 \n"
> + "#fineibt_func: \n"
> + " nopl -42(%rax) \n"
> + "fineibt_preamble_end: \n"
> + ".popsection\n"
> +);
> +
> +extern u8 fineibt_preamble_start[];
> +extern u8 fineibt_preamble_bhi[];
> +extern u8 fineibt_preamble_end[];
> +
> +#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start)
> +#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start)
> +#define fineibt_preamble_ud 0x13
> +#define fineibt_preamble_hash 5
> +
> +#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE)
> +#endif /* CONFIG_FINEIBT */
> +
> #ifdef CONFIG_CFI
> u32 cfi_get_func_hash(void *func)
> {
> @@ -1205,9 +1240,11 @@ u32 cfi_get_func_hash(void *func)
>
> func -= cfi_get_offset();
> switch (cfi_mode) {
> +#ifdef CONFIG_FINEIBT
> case CFI_FINEIBT:
> - func += 7;
> + func += fineibt_preamble_hash;
> break;
> +#endif
> case CFI_KCFI:
> func += 1;
> break;
> @@ -1363,39 +1400,6 @@ early_param("cfi", cfi_parse_cmdline);
> * anyway.
> */
>
> -/*
> - * <fineibt_preamble_start>:
> - * 0: f3 0f 1e fa endbr64
> - * 4: 2d 78 56 34 12 sub $0x12345678, %eax
> - * 9: 2e 0f 85 03 00 00 00 jne,pn 13 <fineibt_preamble_start+0x13>
> - * 10: 0f 1f 40 d6 nopl -0x2a(%rax)
> - *
> - * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as
> - * UDB on x86_64 and raises #UD.
> - */
> -asm( ".pushsection .rodata \n"
> - "fineibt_preamble_start: \n"
> - " endbr64 \n"
> - " subl $0x12345678, %eax \n"
> - "fineibt_preamble_bhi: \n"
> - " cs jne.d32 fineibt_preamble_start+0x13 \n"
> - "#fineibt_func: \n"
> - " nopl -42(%rax) \n"
> - "fineibt_preamble_end: \n"
> - ".popsection\n"
> -);
> -
> -extern u8 fineibt_preamble_start[];
> -extern u8 fineibt_preamble_bhi[];
> -extern u8 fineibt_preamble_end[];
> -
> -#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start)
> -#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start)
> -#define fineibt_preamble_ud 0x13
> -#define fineibt_preamble_hash 5
> -
> -#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE)
> -
> /*
> * <fineibt_caller_start>:
> * 0: b8 78 56 34 12 mov $0x12345678, %eax
>
> ---
> base-commit: 08dbfad3f5040f5bdb6c529da20d6d4e81fefd72
> change-id: 20260831-b4-x86-cfi-fineibt-func-hash-e160f6eee02c
>
> Best regards,
> --
> Soheil Hassas Yeganeh <soheil.kdev@xxxxxxxxx>
>