BPF List
 help / color / mirror / Atom feed
* [RFC PATCH bpf-next v1 0/1] Enable BPF JIT hardening by default when x86_64 CFI is enabled
@ 2026-07-10 19:19 Jennifer Miller
  2026-07-10 19:19 ` [RFC PATCH bpf-next v1 1/1] bpf: Enable " Jennifer Miller
  0 siblings, 1 reply; 4+ messages in thread
From: Jennifer Miller @ 2026-07-10 19:19 UTC (permalink / raw)
  To: ast, daniel, andrii, eddyz87, memxor, kees, linux-hardening
  Cc: jmill, xmei5, samitolvanen, peterz, bpf, linux-kernel

Hi all,

As part of some academic research we, Xiang (CC'd) and I, found three
(closely-related) methods for bypassing different configurations of
x86-64 CFI schemes (kCFI and FineIBT). The methods have some limitations
which I will provide further details on in the rest of this mail.
We previously reported this to security@k.o and were encouraged to have
a public discussion about hardening against this, rather than dealing
with it in private.

In short, the cBPF JIT code can contain controlled 32-bit immediates
which can be used to partially or fully craft CFI hashes and endbr64
instructions to bypass control flow integrity mitigations. To mitigate
these bypasses we'd recommend enabling JIT Constant Blinding for
unprivileged users by default (bpf_jit_harden=1) when CFI is enabled.
I have included a potential patch that does this, I am not sure though
if this change could somehow be more local to CFI related code, or if
it'd be better to modify the function that controls whether JIT blinding
is enabled (bpf_jit_blinding_enabled) rather than touching the sysctl's
default value.

For some additional context, in the past few years misaligned
instructions in the cBPF JIT region have become a somewhat common target
for Control Flow Hijacking exploits because they provide constrained
shellcode execution, a number of submissions to Google's KernelCTF
program have used this approach[1-3]. There is randomization applied to
start of the JIT code so it is probabilistic whether control flow is
hijacked to the expected code, on a system where panic on oops is not
enabled it is possible to retry until the attack succeeds.

We have a PoC exploit for each method, where we took an existing exploit
and demonstrated that we can get code execution from control flow
hijacking on a kernel with CFI enabled, we can share more details about
those about if desired.

There are three methods, each under different configurations:
1. Full Hash Forgery
    - Bypasses kCFI, kernel versions >= 6.2
2. Endbr64 Forgery + Pivot
    - Bypasses FineIBT
3. Opcode Collision Forgery
    - Bypasses kCFI, for kernel versions < 6.2


1. Full Hash Forgery

This method applies when CONFIG_CFI=y and CONFIG_FINEIBT=n or when
CONFIG_FINEIBT=y but kCFI is being used, either as a fallback (AMD) or
forced via `cfi=kcfi` on the kernel cmdline. However, if
CONFIG_FINEIBT=y then a leak of a kCFI hash or the random value that is
xored with the hashes to randomize them (via an arbitrary read or
otherwise) is necessary for this to work.

CFI hashes are four bytes long and it is only possible to forge at most
four consecutive bytes in JIT code using 32-bit immediates. When
CONFIG_FINEIBT=y, the hash is stored at [dest-0xf] (offset depends on
function alignment config but -0xf is where it is at by default) where
'dest' is the target of indirect control flow. If the target hash is
0x21524111 when kCFI is in use, then the negation of the hash is must
be forged in the immediate (-0x21524111 == 0xdeadbeef).

insn0 = {.code = BPF_LD + BPF_K, .k = 0xdeadbeef}; // hash
insn1 = {.code = BPF_LD + BPF_K, .k = 0xcafeb0ba}; // filler
insn2 = {.code = BPF_LD + BPF_K, .k = 0xcafeb0ba}; // filler
insn3 = {.code = BPF_LD + BPF_K, .k = 0xc3d42948}; // code

Produces:
mov eax, 0xdeadbeef
mov eax, 0xcafeb0ba
mov eax, 0xcafeb0ba
mov eax, 0xc3d42948

At the kCFI protected callsite:
[dest-0xf] == 0xdeadbeef (hash check succeeds)
[dest] == 0xc3d42948 == `sub rsp, rdx; ret;` (pivots the stack)


2. Endbr64 Forgery + Pivot

This method applies when CONFIG_FINEIBT=y and FineIBT is used at
run-time. An endbr64 instruction can be crafted in  a 32-bit JIT
immediate and the aligned instructions that follow will be executed
afterwards. This method is *highly* constrained in which callsites can
be used, we have only found one viable callsite so far. If FineIBT
paranoid is enabled, which is true by default for existing Intel CPUs
but not for future CPUs with support for FRED, then a leak of a kCFI
hash is required.

Since the kernel uses rbp as a general purpose register when frame
pointers are omitted it is possible to find call-sites where rbp points
to a heap chunk. BPF JIT code ends in a `leave; ret` sequence, so if rbp
points to the heap then after executing this sequence the heap pointer
will become our new stack when returning from the JIT code, achieving a
stack pivot to the heap. Not only does it need to point to a heap chunk
but [rbp+0x8] must be controlled for the stack pivot to be useful (there
needs to be at least one ROP gadget there to a longer ROP chain
elsewhere in memory). As previously mentioned, we have only found one
case of a call-site where this is possible. Enumerating all call-sites
where this is possible is non-trivial, so we are still unsure of how many
there actually are.


3. Opcode Collision Forgery

This method only really applies when CONFIG_CFI_CLANG=y on kernels with
support for kCFI before FineIBT was merged (pre 6.2). More recent
kernels default to placing padding after the CFI hash rather than before
it. Without FineIBT, CFI hashes are not randomized on boot so a leak is
not necessary.

Prior to 6.2 the padding for CFI stubs was hardcoded to be before the
hash, making it more difficult to forge a full hash in an unaligned JIT
instruction while maintaining control flow hijacking. Using the full
four bytes of a 32-bit immediate to forge a hash would result in aligned
instructions after the hash being executed and would likely result in a
fault when the JIT code tries to return. To maintain code execution
avoid using all four-bytes of the immediate and instead reserve a few
bytes following the hash to craft a jump instruction to the next 4 byte
immediate and continue with the limited shellcode execution in unaligned
instructions. Not crafting all four bytes of the hash in the 32-bit
immediate means that an "opcode-collision" is necessary to craft the
full hash, which looks something like this:

insn0 = {.code = BPF_LD + BPF_K, .k = 0x7fdb0000}; // hash part 1
insn1 = {.code = BPF_LD + BPF_K, .k = 0x9002eb1a}; // hash part 2 + insn

Produces:
mov eax, 0x7fdb0000
mov eax, 0x9002eb1a

Combined:
b8 00 00 [db 7f b8 1a] eb 02 90

At the kCFI protected callsite:
[dest-0x4] == 0x1ab87fdb (hash check succeeds)
[dest] == 0x02eb == `jmp .+4` (continue control flow hijacking)

Since 0xb8 is the opcode byte for the `mov eax` instruction, it is
possible to forge a kCFI hash such as 0x1ab87fdb using two instructions
in the JIT region and still retain limited shellcode execution through
code stored in 32-bit immediates.

There are a number of instructions that take 32-bit immediates that can
be generated in cBPF JIT (e.g., or, xor, and, sub, add, etc...), with
various opcode bytes which can collide with a CFI hash. Xiang enumerated
these instructions and did an analysis to see how many hashes could be
bypassed with this method on a 6.1 LTS kernel (pre-FineIBT so no CFI
hash rerandomization yet, compiled with the KernelCTF config plus the
configs necessary for kCFI), and found that approx 14% of the hashes
were able to be crafted via an opcode collision in the JIT code.


~Jennifer

[1] https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-3609_cos_mitigation/docs/exploit.md#spray-ebpf-programs
[2] https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2024-36972_lts_cos/docs/exploit.md#spray-ebpf-programs
[3] https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2025-21756_cos/docs/exploit.md#spray-ebpf-programs


Jennifer Miller (1):
  bpf: Enable JIT hardening by default when x86_64 CFI is enabled

 kernel/bpf/core.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

--
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [RFC PATCH bpf-next v1 1/1] bpf: Enable JIT hardening by default when x86_64 CFI is enabled
  2026-07-10 19:19 [RFC PATCH bpf-next v1 0/1] Enable BPF JIT hardening by default when x86_64 CFI is enabled Jennifer Miller
@ 2026-07-10 19:19 ` Jennifer Miller
  2026-07-10 19:30   ` sashiko-bot
  2026-07-12  6:22   ` bot+bpf-ci
  0 siblings, 2 replies; 4+ messages in thread
From: Jennifer Miller @ 2026-07-10 19:19 UTC (permalink / raw)
  To: ast, daniel, andrii, eddyz87, memxor, kees, linux-hardening
  Cc: jmill, xmei5, samitolvanen, peterz, bpf, linux-kernel

Initializes bpf_jit_harden to 1 if x86_64 CFI is enabled. Prevents the
misuse of BPF JIT code to craft CFI signatures or Endbr64 instructions
by enabling constant blinding for JITted BPF code from unprivileged
users.

Signed-off-by: Jennifer Miller <jmill@asu.edu>
---
 kernel/bpf/core.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 47fe047ad30b..ed22b90c6e4e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -548,7 +548,11 @@ void bpf_prog_kallsyms_del_all(struct bpf_prog *fp)
 /* All BPF JIT sysctl knobs here. */
 int bpf_jit_enable   __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);
 int bpf_jit_kallsyms __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);
-int bpf_jit_harden   __read_mostly;
+/* Enable hardening by default when x86_64 CFI is enabled to prevent CFI
+ * hashes and endbr64 instructions from being crafted.
+ */
+int bpf_jit_harden   __read_mostly = IS_ENABLED(CONFIG_X86_64) &&
+					IS_ENABLED(CONFIG_CFI);
 long bpf_jit_limit   __read_mostly;
 long bpf_jit_limit_max __read_mostly;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [RFC PATCH bpf-next v1 1/1] bpf: Enable JIT hardening by default when x86_64 CFI is enabled
  2026-07-10 19:19 ` [RFC PATCH bpf-next v1 1/1] bpf: Enable " Jennifer Miller
@ 2026-07-10 19:30   ` sashiko-bot
  2026-07-12  6:22   ` bot+bpf-ci
  1 sibling, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-07-10 19:30 UTC (permalink / raw)
  To: Jennifer Miller; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Initializing `bpf_jit_harden` to `1` by default provides incomplete CFI protection (exempting CAP_BPF users) while unconditionally breaking BPF observability for all users.
- [High] `bpf_jit_harden` is not enabled by default for `CONFIG_X86_KERNEL_IBT` without `CONFIG_CFI`, leaving Endbr64 instructions unprotected against crafted constants.
- [Low] The newly added multi-line comment violates BPF subsystem style guidelines.
--

--- Patch [1]: [RFC PATCH bpf-next v1 1/1] bpf: Enable JIT hardening by default when x86_64 CFI is enabled ---
commit 977257fbaf53ef7b4278e0a06c6f26b8addb7fac
Author: Jennifer Miller <jmill@asu.edu>

bpf: Enable JIT hardening by default when x86_64 CFI is enabled

This commit initializes bpf_jit_harden to 1 by default when x86_64 CFI is
enabled. It aims to prevent unprivileged users from crafting CFI signatures
or Endbr64 instructions by enabling constant blinding for JITted BPF code.

> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -548,7 +548,11 @@ void bpf_prog_kallsyms_del_all(struct bpf_prog *fp)
>  /* All BPF JIT sysctl knobs here. */
>  int bpf_jit_enable   __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);
>  int bpf_jit_kallsyms __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);
> -int bpf_jit_harden   __read_mostly;
> +/* Enable hardening by default when x86_64 CFI is enabled to prevent CFI
> + * hashes and endbr64 instructions from being crafted.
> + */

[Severity: Low]
This isn't a bug, but does this multi-line comment follow the BPF subsystem
formatting rules? The guidelines require the opening slash and asterisk to
be on a line by themselves for multi-line comments.

> +int bpf_jit_harden   __read_mostly = IS_ENABLED(CONFIG_X86_64) &&
> +					IS_ENABLED(CONFIG_CFI);

[Severity: High]
Does initializing bpf_jit_harden to 1 using boolean evaluation create an
issue with observability for privileged users?

Looking at include/linux/filter.h in bpf_jit_kallsyms_enabled(), any
non-zero value for bpf_jit_harden disables kallsyms:

if (bpf_jit_harden)
        return false;

However, looking at bpf_jit_blinding_enabled() in the same file, a value
of 1 explicitly bypasses blinding for users with CAP_BPF:

if (bpf_jit_harden == 1 && bpf_token_capable(prog->aux->token, CAP_BPF))
        return false;

Will this result in breaking BPF observability like profiling and stack
traces for all users, while simultaneously failing to provide the security
benefit of constant blinding for privileged users? Should this be set to 2
instead?

[Severity: High]
Also, does gating this mitigation exclusively on CONFIG_CFI leave Endbr64
instructions unprotected?

The commit message mentions preventing endbr64 instructions from being
crafted, but endbr64 is tied to hardware Indirect Branch Tracking (IBT)
which uses CONFIG_X86_KERNEL_IBT.

If a kernel is compiled with IBT enabled but CONFIG_CFI disabled, the
boolean logic will evaluate to 0, leaving the JIT vulnerable to 32-bit
constants corresponding to endbr64 gadgets.

Should CONFIG_X86_KERNEL_IBT be included in this evaluation to fully cover
the protections mentioned in the commit message?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260710191932.120911-2-jmill@asu.edu?part=1

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [RFC PATCH bpf-next v1 1/1] bpf: Enable JIT hardening by default when x86_64 CFI is enabled
  2026-07-10 19:19 ` [RFC PATCH bpf-next v1 1/1] bpf: Enable " Jennifer Miller
  2026-07-10 19:30   ` sashiko-bot
@ 2026-07-12  6:22   ` bot+bpf-ci
  1 sibling, 0 replies; 4+ messages in thread
From: bot+bpf-ci @ 2026-07-12  6:22 UTC (permalink / raw)
  To: jmill, ast, daniel, andrii, eddyz87, memxor, kees,
	linux-hardening
  Cc: jmill, xmei5, samitolvanen, peterz, bpf, linux-kernel, ast,
	andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
	ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 911 bytes --]

> bpf: Enable JIT hardening by default when x86_64 CFI is enabled
>
>     Initializes bpf_jit_harden to 1 if x86_64 CFI is enabled. Prevents the
>     misuse of BPF JIT code to craft CFI signatures or Endbr64 instructions
>     by enabling constant blinding for JITted BPF code from unprivileged
>     users.
>
>     Signed-off-by: Jennifer Miller <jmill@asu.edu>

This closes a hardening gap that only exists once x86_64 can enable CFI,
which came in with the x86_64 CFI support that added
'select ARCH_SUPPORTS_CFI_CLANG if X86_64'.  Should this carry a Fixes:
tag pointing at that commit, for example:

  Fixes: 3c516f89e17e ("x86: Add support for CONFIG_CFI_CLANG")


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/29118478293

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-07-12  6:22 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 19:19 [RFC PATCH bpf-next v1 0/1] Enable BPF JIT hardening by default when x86_64 CFI is enabled Jennifer Miller
2026-07-10 19:19 ` [RFC PATCH bpf-next v1 1/1] bpf: Enable " Jennifer Miller
2026-07-10 19:30   ` sashiko-bot
2026-07-12  6:22   ` bot+bpf-ci

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox