powerpc/bpf: address missing verifier selftest coverage
Subsystems (6)
v13 cover powerpc/bpf: address missing verifier selftest coverage
From: Abhishek Dubey <[email protected]>
The verifier selftest validates JITed instructions by matching expected disassembly output. The first two patches fix issues in powerpc instruction disassembly that were causing test flow failures. The fix is common for 64-bit & 32-bit powerpc. Add support for the powerpc-specific "__powerpc64" architecture tag in the third patch, enabling proper test filtering in verifier test files. Introduce verifier testcases for tailcalls on powerpc64.
The first patch in series is fix patch, correcting memory alignment with 8-byte boundary for long branch address field. The subsequent patches enables verifier selftests on powerpc. The fifth patch in the series fixes incorrect comparator usage for comparing tailcall info with tailcall threshold. The last two patches fix JIT buffer overflow for large BPF progs and private stack memory leak (identified by bot during reviews).
Issue Details:
--------------
The Long branch stub in the trampoline implementation[1] provides
flexibility to handles short as well as long branch distance to
actual trampoline. Whereas, the 8 bytes long dummy_tramp_addr field
sitting before long branch stub leads to failure when enabling
verifier based seltest for ppc64.
The verifier selftests require disassembing the final jited image
to get native instructions. Later the disassembled instruction
sequence is matched against sequence of instructions provided in
test-file under __jited() wrapper. The final jited image contains
Out-of-line stub and Long branch stub as part of epilogue jitting
for a bpf program. The 8 bytes space for dummy_tramp is sandwiched
between both above mentioned stubs. These 8 bytes contain memory
address of dummy trampoline during trampoline invocation which don't
correspond to any powerpc instructions. So, disassembly fails
resulting in failure of verifier selftests.
The following code snippet shows the problem with current arrangement
made for dummy_tramp_addr.
/* Out-of-line stub */
mflr r0
[b|bl] tramp
mtlr r0 //only with OOL
b bpf_func + 4
/* Long branch stub */
.long <dummy_tramp_addr> <---Invalid bytes sequence, disassembly fails
mflr r11
bcl 20,31,$+4
mflr r12
ld r12, -8-SZL(r12)
mtctr r12
mtlr r11 //retain ftrace ABI
bctr
Consider test program binary of size 112 bytes:
0: 00000060 10004de8 00002039 f8ff21f9 81ff21f8 7000e1fb 3000e13b
28: 3000e13b 2a006038 f8ff7ff8 00000039 7000e1eb 80002138 7843037d
56: 2000804e a602087c 00000060 a603087c bcffff4b c0341d00 000000c0
84: a602687d 05009f42 a602887d f0ff8ce9 a603897d a603687d 2004804e
Disassembly output of above binary for ppc64le:
pc:0 left:112 00 00 00 60 : nop
pc:4 left:108 10 00 4d e8 : ld 2, 16(13)
pc:8 left:104 00 00 20 39 : li 9, 0
pc:12 left:100 f8 ff 21 f9 : std 9, -8(1)
pc:16 left:96 81 ff 21 f8 : stdu 1, -128(1)
pc:20 left:92 70 00 e1 fb : std 31, 112(1)
pc:24 left:88 30 00 e1 3b : addi 31, 1, 48
pc:28 left:84 30 00 e1 3b : addi 31, 1, 48
pc:32 left:80 2a 00 60 38 : li 3, 42
pc:36 left:76 f8 ff 7f f8 : std 3, -8(31)
pc:40 left:72 00 00 00 39 : li 8, 0
pc:44 left:68 70 00 e1 eb : ld 31, 112(1)
pc:48 left:64 80 00 21 38 : addi 1, 1, 128
pc:52 left:60 78 43 03 7d : mr 3, 8
pc:56 left:56 20 00 80 4e : blr
pc:60 left:52 a6 02 08 7c : mflr 0
pc:64 left:48 00 00 00 60 : nop
pc:68 left:44 a6 03 08 7c : mtlr 0
pc:72 left:40 bc ff ff 4b : b .-68
pc:76 left:36 c0 34 1d 00 :
...
Failure log:
Can't disasm instruction at offset 76: c0 34 1d 00 00 00 00 c0 a6 02 68 7d 05 00 9f 42
--------------------------------------
Observation:
Can't disasm instruction at offset 76 as this address has
".long <dummy_tramp_addr>" (0xc0341d00000000c0)
But valid instructions follow at offset 84 onwards.
Move the long branch address space to the bottom of the long
branch stub. This allows uninterrupted disassembly until the
last 8 bytes. Exclude these last bytes from the overall
program length to prevent failure in assembly generation.
Following is disassembler output for same test program with moved down
dummy_tramp_addr field:
.....
.....
pc:68 left:44 a6 03 08 7c : mtlr 0
pc:72 left:40 bc ff ff 4b : b .-68
pc:76 left:36 a6 02 68 7d : mflr 11
pc:80 left:32 05 00 9f 42 : bcl 20, 31, .+4
pc:84 left:28 a6 02 88 7d : mflr 12
pc:88 left:24 14 00 8c e9 : ld 12, 20(12)
pc:92 left:20 a6 03 89 7d : mtctr 12
pc:96 left:16 a6 03 68 7d : mtlr 11
pc:100 left:12 20 04 80 4e : bctr
pc:104 left:8 c0 34 1d 00 :
Failure log:
Can't disasm instruction at offset 104: c0 34 1d 00 00 00 00 c0
---------------------------------------
Disassembly logic can truncate at 104, ignoring last 8 bytes.
Update the dummy_tramp_addr field offset calculation from the end
of the program to reflect its new location, for bpf_arch_text_poke()
to update the actual trampoline's address in this field.
[1] https://lore.kernel.org/all/[email protected]v12->v13:
Moved patch 4 and 5 before 1.
Modified comments in code.
v11->v12:
Fix build failure for ppc32
Rearranged the order of patches as suggested by Hari
v10->v11:
Moved function prototype change from patch 2 to patch 1 Fixed commit message for patch 1 and patch 4 Fixed extable entry generation condition
Added reviewed and acked tags by Hari
v9->v10:
Modified expected JIT instruction in selftest for CONFIG_PPC_KERNEL_PCREL incase of PPC_LI64 instruction. Fix for stale entries in exception table
Update ARCH_POWERPC64 enum value to 0x40 to resolve rebase conflict with ARCH_LOONGARCH (0x20).
v8->v9:
Dynamic pass handling until code keeps shrinking Fix private stack memory leak
v7->v8:
Fixed bot identified issues of alt_exit_addr and BPF_EXIT Fixed 32-bit ppc function signature mismatch
v6->v7:
Fixed JIT buffer overflow in case of large BPF progs Addressed remaining bot comments
v5->v6:
Changed alignment NOP emittion dependency on fimage layout Adjust tail truncate length for 32-bit ppc Addressed few minor bot comments
v4->v5:
Handled alignment NOP emit logic and corresponding stub offsets Handled image buffer overflow problem in last pass Above changes took care of other bot reviews
Included LLVMDisposeMessage() for graceful freeing Adjusted parameters in bpf_jit_build_fentry_stubs for ppc32 Adjusted expected JIT inst. in tailcall test forCONFIG_PPC_KERNEL_PCREL config
Added fix patch at last for inaccurate use of cmplwi inst.
v3->v4:
Changed logic for emitting alignment NOP
v2->v3:
Removed fixed NOP from bottom of long branch stub Rebased on top of bpf-next
v1->v2:
Added fix-patch to correct memory alignment in-place Moved the optional alignmnet NOP before OOL stub
[v1]: https://lore.kernel.org/bpf/[email protected]
[v2]: https://lore.kernel.org/bpf/[email protected]
[v3]: https://lore.kernel.org/bpf/[email protected]
[v4]: https://lore.kernel.org/bpf/[email protected]
[v5]: https://lore.kernel.org/bpf/[email protected]
[v6]: https://lore.kernel.org/bpf/[email protected]
[v7]: https://lore.kernel.org/bpf/[email protected]
[v8]: https://lore.kernel.org/bpf/[email protected]
[v9]: https://lore.kernel.org/bpf/[email protected]/
[v10]: https://lore.kernel.org/all/[email protected]/
[v11]: https://lore.kernel.org/all/[email protected]/#r
[v12]: https://lore.kernel.org/all/[email protected]/
Abhishek Dubey (8):
powerpc64/bpf: fix compare instruction emitted for tailcall powerpc64/bpf: fix percpu private stack leak on JIT failure powerpc/bpf: fix buffer overflow in JIT for large BPF programs powerpc/bpf: fix alignment of long branch trampoline address powerpc/bpf: Move out dummy_tramp_addr after Long branch stub selftests/bpf: Fix powerpc JIT disassembly failure selftests/bpf: Enable verifier selftest for powerpc64 selftests/bpf: Add tailcall verifier selftest for powerpc64
arch/powerpc/net/bpf_jit.h | 20 +++- arch/powerpc/net/bpf_jit_comp.c | 100 +++++++++++++----- arch/powerpc/net/bpf_jit_comp32.c | 7 +- arch/powerpc/net/bpf_jit_comp64.c | 15 +-- .../selftests/bpf/jit_disasm_helpers.c | 25 ++++- tools/testing/selftests/bpf/progs/bpf_misc.h | 1 + .../bpf/progs/verifier_tailcall_jit.c | 78 ++++++++++++++ tools/testing/selftests/bpf/test_loader.c | 5 + 8 files changed, 211 insertions(+), 40 deletions(-)
Signature · 1 line
2.55.0
PATCH v13 1/8 powerpc64/bpf: fix compare instruction emitted for tailcall
From: Abhishek Dubey <[email protected]>
The tail_call_info field can contain either a scalar counter value or a 64-bit pointer to the counter, using a 32-bit compare (cmplwi) only checks the lower 32 bits, which can lead to incorrect comparisions when location of counter is near 4GB boundary. Use instruction cmpldi/cmplwi for accurate comparision in corresponding cases.
--- arch/powerpc/net/bpf_jit.h | 6 ++++++ arch/powerpc/net/bpf_jit_comp.c | 2 +- arch/powerpc/net/bpf_jit_comp64.c | 8 ++++---- 3 files changed, 11 insertions(+), 5 deletions(-)
arch/powerpc/net/bpf_jit.h +6 -0
@@ -188,6 +188,12 @@ struct codegen_context {#define bpf_to_ppc (r) (ctx->b2p[r]) +#ifdef CONFIG_PPC64 +#define PPC_RAW_CMPLLI (a, i) PPC_RAW_CMPLDI(a, i) +#else +#define PPC_RAW_CMPLLI (a, i) PPC_RAW_CMPLWI(a, i) +#endif +#ifdef CONFIG_PPC32 #define BPF_FIXUP_LEN 3/* Three instructions => 12 bytes */ #else
arch/powerpc/net/bpf_jit_comp.c +1 -1
@@ -739,7 +739,7 @@ static void bpf_trampoline_setup_tail_call_info(u32 *image, struct codegen_conte* Setting the tail_call_info in trampoline's frame * depending on if previous frame had value or reference. */ -EMIT (PPC_RAW_CMPLWI (_R3,MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLLI (_R3,MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_GT ,8 );EMIT (PPC_RAW_ADDI (_R3, _R4, -BPF_PPC_TAILCALL ));
arch/powerpc/net/bpf_jit_comp64.c +4 -4
@@ -276,7 +276,7 @@ void bpf_jit_build_prologue(u32 *image, struct codegen_context *ctx)*/ EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_2 ), _R1,0 ));EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_1 ),bpf_to_ppc (TMP_REG_2 ), -(BPF_PPC_TAILCALL ))); -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_GT ,8 );EMIT (PPC_RAW_ADDI (bpf_to_ppc (TMP_REG_1 ),bpf_to_ppc (TMP_REG_2 ), -(BPF_PPC_TAILCALL )));
@@ -662,7 +662,7 @@ static int bpf_jit_emit_tail_call(u32 *image, struct codegen_context *ctx, u32 oPPC_BCC_SHORT (COND_GE , out);EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_1 ), _R1,bpf_jit_stack_tailcallinfo_offset (ctx))); -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_LE ,8 );/* dereference TMP_REG_1 */
@@ -672,7 +672,7 @@ static int bpf_jit_emit_tail_call(u32 *image, struct codegen_context *ctx, u32 o* if (tail_call_info == MAX_TAIL_CALL_CNT) * goto out; */ -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT ));PPC_BCC_SHORT (COND_EQ , out);/*
@@ -707,7 +707,7 @@ static int bpf_jit_emit_tail_call(u32 *image, struct codegen_context *ctx, u32 o* tail_call_info. */ EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_2 ), _R1,bpf_jit_stack_tailcallinfo_offset (ctx))); -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_2 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_2 ),MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_GT ,8 );/* First get address of tail_call_info */
Signature · 1 line
2.55.0
Re: [PATCH v13 1/8] powerpc64/bpf: fix compare instruction emitted for tailcall
Quoted 9 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
The
tail_call_infofield can contain either a scalar counter value or a 64-bit pointer to the counter, using a 32-bit compare (cmplwi) only checks the lower 32 bits, which can lead to incorrect comparisions when location of counter is near 4GB boundary. Use instruction cmpldi/cmplwi for accurate comparision in corresponding cases.
Quoted 7 lines
Reported-by [email protected]Fixes 2ed2d8f6fb38 ("powerpc64/bpf: Support tailcalls with subprogs")Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>Acked-by Hari Bathini <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 76 lines
--- arch/powerpc/net/bpf_jit.h | 6 ++++++ arch/powerpc/net/bpf_jit_comp.c | 2 +- arch/powerpc/net/bpf_jit_comp64.c | 8 ++++---- 3 files changed, 11 insertions(+), 5 deletions(-)arch/powerpc/net/bpf_jit.h +6
-0@@ -188,6 +188,12 @@ struct codegen_context {#define bpf_to_ppc (r) (ctx->b2p[r]) +#ifdef CONFIG_PPC64 +#define PPC_RAW_CMPLLI (a, i) PPC_RAW_CMPLDI(a, i) +#else +#define PPC_RAW_CMPLLI (a, i) PPC_RAW_CMPLWI(a, i) +#endif +#ifdef CONFIG_PPC32 #define BPF_FIXUP_LEN 3/* Three instructions => 12 bytes */ #else arch/powerpc/net/bpf_jit_comp.c +1
-1@@ -739,7 +739,7 @@ static void bpf_trampoline_setup_tail_call_info(u32 *image, struct codegen_conte* Setting the tail_call_info in trampoline's frame * depending on if previous frame had value or reference. */ -EMIT (PPC_RAW_CMPLWI (_R3,MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLLI (_R3,MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_GT ,8 );EMIT (PPC_RAW_ADDI (_R3, _R4, -BPF_PPC_TAILCALL ));arch/powerpc/net/bpf_jit_comp64.c +4
-4@@ -276,7 +276,7 @@ void bpf_jit_build_prologue(u32 *image, struct codegen_context *ctx)*/ EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_2 ), _R1,0 ));EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_1 ),bpf_to_ppc (TMP_REG_2 ), -(BPF_PPC_TAILCALL ))); -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_GT ,8 );EMIT (PPC_RAW_ADDI (bpf_to_ppc (TMP_REG_1 ),bpf_to_ppc (TMP_REG_2 ), -(BPF_PPC_TAILCALL )));@@ -662,7 +662,7 @@ static int bpf_jit_emit_tail_call(u32 *image, struct codegen_context *ctx, u32 oPPC_BCC_SHORT (COND_GE , out);EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_1 ), _R1,bpf_jit_stack_tailcallinfo_offset (ctx))); -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_LE ,8 );/* dereference TMP_REG_1 */ @@ -672,7 +672,7 @@ static int bpf_jit_emit_tail_call(u32 *image, struct codegen_context *ctx, u32 o* if (tail_call_info == MAX_TAIL_CALL_CNT) * goto out; */ -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_1 ),MAX_TAIL_CALL_CNT ));PPC_BCC_SHORT (COND_EQ , out);/* @@ -707,7 +707,7 @@ static int bpf_jit_emit_tail_call(u32 *image, struct codegen_context *ctx, u32 o* tail_call_info. */ EMIT (PPC_RAW_LD (bpf_to_ppc (TMP_REG_2 ), _R1,bpf_jit_stack_tailcallinfo_offset (ctx))); -EMIT (PPC_RAW_CMPLWI (bpf_to_ppc (TMP_REG_2 ),MAX_TAIL_CALL_CNT )); +EMIT (PPC_RAW_CMPLDI (bpf_to_ppc (TMP_REG_2 ),MAX_TAIL_CALL_CNT ));PPC_BCC_CONST_SHORT (COND_GT ,8 );/* First get address of tail_call_info */
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry
PATCH v13 2/8 powerpc64/bpf: fix percpu private stack leak on JIT failure
In reply to Saket Kumar BhaskarFrom: Abhishek Dubey <[email protected]>
The existing conditional statement in bpf_int_jit_compile() frees the percpu private stack at out_addrs only when the image buffer was never allocated.
If bpf_jit_build_body() fails during a code-generation pass, the image buffer has already been allocated, so !image is false and the percpu stack is not freed.
Because JIT compilation failed, fp->jited remains at 0. The subsequent bpf_jit_free() path only frees priv_stack_ptr when fp->jited is set, so freeing is skipped here too, leaking the percpu allocation.
Fix implements freeing the private stack whenever fp->jited was not set, i.e. compilation did not succeed, instead of keying off !image. !fp->jited already covers the !image case, since image is only NULL on early-failure paths where fp->jited is likewise 0.
--- arch/powerpc/net/bpf_jit_comp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/powerpc/net/bpf_jit_comp.c +1 -1
@@ -357,7 +357,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr (void *)fimage + FUNCTION_DESCR_SIZE); out_addrs: -if (!image && priv_stack_ptr) { +if (!fp->jited && priv_stack_ptr) { fp->aux->priv_stack_ptr =NULL ;free_percpu (priv_stack_ptr); }
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v13 2/8] powerpc64/bpf: fix percpu private stack leak on JIT failure
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The ARM64 BPF JIT compiler leaks the percpu private stack on JIT failure.
- [High] bpf_jit_binary_pack_finalize uses uninitialized memory for size on failure, causing a system crash and leaking modmem charges. --
commit a7adca4200815295703e0e825b2bdfcafd6b972e
Author: Abhishek Dubey <[email protected]>
powerpc64/bpf: fix percpu private stack leak on JIT failure
The existing conditional statement in bpf_int_jit_compile() frees the percpu private stack only when the image buffer was never allocated. If bpf_jit_build_body() fails, the image buffer is already allocated, so the percpu stack is not freed and leaks. This fix implements freeing the private stack whenever fp->jited was not set, i.e., compilation did not succeed.
arch/powerpc/net/bpf_jit_comp.c struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr · line 363
Quoted 13 lines
arch/powerpc/net/bpf_jit_comp.c +1
-1@@ -357,7 +357,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr (void *)fimage + FUNCTION_DESCR_SIZE); out_addrs: -if (!image && priv_stack_ptr) { +if (!fp->jited && priv_stack_ptr) { fp->aux->priv_stack_ptr =NULL ;free_percpu (priv_stack_ptr); }
arch/arm64/net/bpf_jit_comp.c:bpf_int_jit_compile() {
...out_off:
if (!ro_header && priv_stack_ptr) {
free_percpu(priv_stack_ptr);
prog->aux->priv_stack_ptr = NULL;
}...
}
[Severity: High]
This isn't a bug introduced by this patch, but while reviewing this fix I noticed a similar issue in the ARM64 JIT compiler. Does this code leak the percpu private stack if JIT compilation fails after header allocation?
If bpf_jit_build_body() fails, the error path frees the header but does not nullify the ro_header pointer. The subsequent cleanup block evaluates to false, failing to free priv_stack_ptr. Since prog->jited remains 0, bpf_jit_free() also skips freeing it.
kernel/bpf/core.c:bpf_jit_binary_pack_finalize() {
...
ptr = bpf_arch_text_copy(ro_header, rw_header, rw_header->size);
kvfree(rw_header);
if (IS_ERR(ptr)) {
bpf_prog_pack_free(ro_header, ro_header->size);
return PTR_ERR(ptr);
}...
}
[Severity: High]
This is a pre-existing issue, but I spotted a potential memory corruption and resource leak in the BPF core during JIT finalize. Can this error path cause a system crash and leak modmem charges?
If bpf_arch_text_copy() fails, the size field in ro_header remains uninitialized (for example, filled with breakpoint instructions by bpf_fill_ill_insns()). Calling bpf_prog_pack_free() with this wild size could corrupt the allocator.
Also, does this error path permanently leak the module memory charge by returning without calling bpf_jit_uncharge_modmem(size)?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v13 2/8] powerpc64/bpf: fix percpu private stack leak on JIT failure
In reply to Saket Kumar BhaskarQuoted 19 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
The existing conditional statement in
bpf_int_jit_compile()frees the percpu private stack atout_addrsonly when the image buffer was never allocated.If
bpf_jit_build_body()fails during a code-generation pass, the image buffer has already been allocated, so !image is false and the percpu stack is not freed.Because JIT compilation failed,
fp->jitedremains at 0. The subsequentbpf_jit_free()path only freespriv_stack_ptrwhenfp->jitedis set, so freeing is skipped here too, leaking the percpu allocation.Fix implements freeing the private stack whenever
fp->jitedwas not set, i.e. compilation did not succeed, instead of keying off !image. !fp->jited already covers the !image case, since image is only NULL on early-failure paths wherefp->jitedis likewise 0.
Quoted 8 lines
Reported-by [email protected]Fixes 156d985123b6 ("powerpc64/bpf: Implement JIT support for private stack")Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>Acked-by Hari Bathini <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 17 lines
--- arch/powerpc/net/bpf_jit_comp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)arch/powerpc/net/bpf_jit_comp.c +1
-1@@ -357,7 +357,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr (void *)fimage + FUNCTION_DESCR_SIZE); out_addrs: -if (!image && priv_stack_ptr) { +if (!fp->jited && priv_stack_ptr) { fp->aux->priv_stack_ptr =NULL ;free_percpu (priv_stack_ptr); }
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry
PATCH v13 3/8 powerpc/bpf: fix buffer overflow in JIT for large BPF programs
In reply to Saket Kumar BhaskarFrom: Abhishek Dubey <[email protected]>
During size calculation in pass-0, exit_addr is 0 since addrs[fp->len] is not yet populated. bpf_jit_emit_exit_insn() treats a zero exit_addr as in-range and skips bpf_jit_build_epilogue(), so the alternate inline epilogue instructions are not counted in alloclen.
In later passes, if the real exit_addr falls outside the 32MB branch range, the full inline epilogue is emitted into the already-allocated buffer, writing past its end and corrupting adjacent memory.
Fix by ensuring exit_addr is non-zero before treating it as in-range, so pass-0 always falls through to bpf_jit_build_epilogue() and conservatively accounts for all epilogue instructions in alloclen. Also range check alt_exit_addr directly in the else-if condition.
Since exit_addr handling now falls through to the epilogue, two related issues in bpf_int_jit_compile() must also be addressed:
1. Reset cgctx.alt_exit_addr before the second size-calculation pass. Without this, a stale alt_exit_addr from the first pass causes the second pass to emit a single jump instead of the full epilogue, undercounting alloclen and reintroducing the overflow.
2. Recompute addrs[fp->len] at the end of each code-generation pass. The larger pass-0 body can shrink in later passes as out-of-range exits settle into in-range jumps; a stale addrs[fp->len] would leave exit branches targeting past the real (shrunken) epilogue.
Because shrinkage in a later pass can move the epilogue offset, the fixed two-pass loop is no longer sufficient: an exit that was out of range in an earlier pass may fall in range once the epilogue offset shrinks, shrinking the body further and overwriting the start of the epilogue. Convert the code-generation loop to iterate until the program size converges, bounded by CODEGEN_MAX_PASSES, and fail the JIT if it does not converge.
--- arch/powerpc/net/bpf_jit.h | 7 +++++++ arch/powerpc/net/bpf_jit_comp.c | 34 +++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-)
arch/powerpc/net/bpf_jit.h +7 -0
@@ -14,6 +14,13 @@#include <asm/ppc-opcode.h> #include <linux/build_bug.h> +/* +* We need at least 2 passes for proper code generation, and may need +* additional passes if code size changes between passes. +*/ +#define CODEGEN_MIN_PASSES 2 +#define CODEGEN_MAX_PASSES 3 +#ifdef CONFIG_PPC64_ELF_ABI_V1 #define FUNCTION_DESCR_SIZE 24#else
arch/powerpc/net/bpf_jit_comp.c +26 -8
@@ -99,11 +99,10 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr) { -if (!exit_addr ||is_offset_in_branch_range (exit_addr - (ctx->idx *4 ))) { +if (exit_addr &&is_offset_in_branch_range (exit_addr - (long )(ctx->idx *4 ))) {PPC_JMP (exit_addr); - }else if (ctx->alt_exit_addr) { -if (WARN_ON (!is_offset_in_branch_range ((long )ctx->alt_exit_addr - (ctx->idx *4 )))) -return -1 ; + }else if (ctx->alt_exit_addr &&is_offset_in_branch_range ( + (long )(ctx->alt_exit_addr) - (long )(ctx->idx *4 ))) {PPC_JMP (ctx->alt_exit_addr); }else { ctx->alt_exit_addr = ctx->idx *4 ;
@@ -274,6 +273,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr*/ if (cgctx.seen &SEEN_TAILCALL || !is_offset_in_branch_range ((long )cgctx.idx *4 )) { cgctx.idx =0 ; + cgctx.alt_exit_addr =0 ;if (bpf_jit_build_body (fp,NULL ,NULL , &cgctx, addrs,0 , false)) goto out_err; }
@@ -306,10 +306,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr code_base = (u32 *)(image +FUNCTION_DESCR_SIZE ); fcode_base = (u32 *)(fimage +FUNCTION_DESCR_SIZE ); -/* Code generation passes 1-2 */ -for (pass =1 ; pass <3 ; pass++) { +/* Code generation passes 1-2+, loop until program size converges. */ +for (pass =1 ; pass <=CODEGEN_MAX_PASSES ; pass++) { +u32 prev_proglen = proglen; +/* Now build the prologue, body code & epilogue for real. */ cgctx.idx =0 ; + cgctx.exentry_idx =0 ; cgctx.alt_exit_addr =0 ;bpf_jit_build_prologue (code_base, &cgctx); if (bpf_jit_build_body(fp, code_base, fcode_base, &cgctx, addrs, pass,
@@ -318,11 +321,26 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prbpf_jit_binary_pack_free (fhdr, hdr); goto out_err; } + addrs[fp->len] = cgctx.idx *4 ;bpf_jit_build_epilogue (code_base, &cgctx); + proglen = cgctx.idx *4 ; +if (bpf_jit_enable >1 )pr_info ("Pass %d: shrink = %d, seen = 0x%x\n" , pass, - proglen - (cgctx.idx *4 ), cgctx.seen); + prev_proglen - proglen, cgctx.seen); + +/* Check if program size has converged, but ensure minimum passes */ +if (pass >=CODEGEN_MIN_PASSES && proglen == prev_proglen) +break ; + +if (pass ==CODEGEN_MAX_PASSES && proglen != prev_proglen) { +pr_err ("BPF JIT: Program did not converge after %d passes\n" , +CODEGEN_MAX_PASSES ); +bpf_arch_text_copy (&fhdr->size, &hdr->size,sizeof (hdr->size)); +bpf_jit_binary_pack_free (fhdr, hdr); + goto out_err; + } }if (bpf_jit_enable >1 )
@@ -399,7 +417,7 @@ int bpf_add_extable_entry(struct bpf_prog *fp, u32 *image, u32 *fimage, int passu32 *fixup;/* Populate extable entries only in the last pass */ -if (pass !=2 ) +if (pass <CODEGEN_MIN_PASSES )return 0 ; if (!fp->aux->extable ||
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v13 3/8] powerpc/bpf: fix buffer overflow in JIT for large BPF programs
Does the Fixes: tag name the correct commit?
Quoted 1 line
Fixes d243b62b7bd3 ("powerpc64/bpf: Add support for bpf trampolines")
Does the Fixes: tag name the correct commit?
With Cc: [email protected] this fix will only reach v6.13+ and will leave every stable tree from v5.18 to v6.12 carrying the same JIT buffer overflow.
The faulty logic predates d243b62b7bd3. Looking at the parent of that commit:
git show d243b62b7bd3^:arch/powerpc/net/bpf_jit_comp.c
already contains the exact code being fixed:
if (!exit_addr || is_offset_in_branch_range(exit_addr - (ctx->idx * 4))) {
PPC_JMP(exit_addr);
} else if (ctx->alt_exit_addr) {
if (WARN_ON(!is_offset_in_branch_range((long)ctx->alt_exit_addr - (ctx->idx * 4))))
return -1;
PPC_JMP(ctx->alt_exit_addr);
} else {
ctx->alt_exit_addr = ctx->idx * 4;
bpf_jit_build_epilogue(image, ctx);
}The real introducing commit is 0ffdbce6f4a89bb7c0002904d6438ec83cf05ce7 ("powerpc/bpf: Handle large branch ranges with BPF_EXIT"), merged in v5.18-rc1 (git describe --contains: v5.18-rc1~124^2~42). It created bpf_jit_emit_exit_insn() with the '!exit_addr' early-out and replaced the previous unconditional one-instruction emission:
- if (i != flen - 1)
- PPC_JMP(exit_addr);
+ if (i != flen - 1) {
+ ret = bpf_jit_emit_exit_insn(image, ctx, b2p[TMP_REG_1], exit_addr);
Before 0ffdbce6f4a8 a non-final BPF_EXIT always cost exactly one instruction in every pass, so the sizing pass could not under-count and no overflow was possible. After it, the sizing pass emits PPC_JMP(0) (1 instruction) while later passes may emit a full inline epilogue, which is precisely the under-count described in this commit message.
The commit d243b62b7bd3 only enlarged the epilogue (it added bpf_jit_build_fentry_stubs()), making an already-existing overflow bigger.
Citing the ppc64-trampoline commit also wrongly implies PPC32 is unaffected. The commit 0ffdbce6f4a8 made the identical change to arch/powerpc/net/bpf_jit_comp32.c, so 32-bit powerpc has the same bug over the same range.
A tag survey confirms the range: 'alt_exit_addr' has 0 occurrences in arch/powerpc/net/ at v5.16 and v5.17, and 5 occurrences at v5.18, v5.19, v6.0, v6.1 and v6.6.
Should the tag be:
Quoted 6 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0@@ -99,11 +99,10 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)
arch/powerpc/net/bpf_jit_comp.c void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx) · line 105
Quoted 10 lines
arch/powerpc/net/bpf_jit_comp.c +3
-4int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr) { -if (!exit_addr ||is_offset_in_branch_range (exit_addr - (ctx->idx *4 ))) { +if (exit_addr &&is_offset_in_branch_range (exit_addr - (long )(ctx->idx *4 ))) {PPC_JMP (exit_addr); - }else if (ctx->alt_exit_addr) { -if (WARN_ON (!is_offset_in_branch_range ((long )ctx->alt_exit_addr - (ctx->idx *4 )))) -return -1 ; + }else if (ctx->alt_exit_addr &&is_offset_in_branch_range ( + (long )(ctx->alt_exit_addr) - (long )(ctx->idx *4 ))) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
arch/powerpc/net/bpf_jit_comp.c void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx) · line 106
Quoted 1 line
arch/powerpc/net/bpf_jit_comp.c +0
-0PPC_JMP (ctx->alt_exit_addr);
With alt_exit_addr defined as unsigned int, does the expression (long)(ctx->alt_exit_addr) - (long)(ctx->idx * 4) produce a different result than (long)ctx->alt_exit_addr - (ctx->idx * 4) due to how integer promotion works?
In v7 review, Hari Bathini pointed out that the cast placement may affect integer promotion semantics and requested restoring the earlier syntax with the cast only on alt_exit_addr:
https://lore.kernel.org/bpf/[email protected]
While the current code has passed testing, the concern about integer promotion semantics was never explicitly addressed.
[ ... ]
arch/powerpc/net/bpf_jit_comp.c int bpf_add_extable_entry(struct bpf_prog *fp, u32 *image, u32 *fimage, int pass · line 421
Quoted 7 lines
arch/powerpc/net/bpf_jit_comp.c +1
-1@@ -399,7 +417,7 @@ int bpf_add_extable_entry(struct bpf_prog *fp, u32 *image, u32 *fimage, int passu32 *fixup;/* Populate extable entries only in the last pass */ -if (pass !=2 ) +if (pass <CODEGEN_MIN_PASSES )return 0 ;
Does the comment still match the code?
With the old 'if (pass != 2)' and a fixed two-pass loop, pass 2 was the last pass, so the comment was accurate. With the new loop and 'pass < CODEGEN_MIN_PASSES', both pass 2 and pass 3 can write extable entries, so entries are populated in every pass from 2 onwards, not only in the last one.
Also, CODEGEN_MIN_PASSES is being reused here with a different meaning. In arch/powerpc/net/bpf_jit.h it documents the minimum number of code-generation passes, but here it is used as 'the first pass in which extable entries are valid'. The two are logically independent - bumping CODEGEN_MIN_PASSES to 3 to force more settling iterations would silently stop extable entries being written in pass 2.
---
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/33368376983
Hari Bathini
maintainerRe: [PATCH v13 3/8] powerpc/bpf: fix buffer overflow in JIT for large BPF programs
Quoted 50 lines · On 31/08/26 1:54 pm, [email protected] wrote:
Quoted 1 line
Fixes d243b62b7bd3 ("powerpc64/bpf: Add support for bpf trampolines")Does the Fixes: tag name the correct commit?
With Cc: [email protected] this fix will only reach v6.13+ and will leave every stable tree from v5.18 to v6.12 carrying the same JIT buffer overflow.
The faulty logic predates
d243b62b7bd3. Looking at the parent of that commit:git show d243b62b7bd3^:arch/powerpc/net/bpf_jit_comp.c
already contains the exact code being fixed:
if (!exit_addr || is_offset_in_branch_range(exit_addr - (ctx->idx * 4))) { PPC_JMP(exit_addr); } else if (ctx->alt_exit_addr) { if (WARN_ON(!is_offset_in_branch_range((long)ctx->alt_exit_addr - (ctx->idx * 4)))) return -1; PPC_JMP(ctx->alt_exit_addr); } else { ctx->alt_exit_addr = ctx->idx * 4; bpf_jit_build_epilogue(image, ctx); }The real introducing commit is
0ffdbce6f4a89bb7c0002904d6438ec83cf05ce7("powerpc/bpf: Handle large branch ranges withBPF_EXIT"), merged in v5.18-rc1 (git describe --contains: v5.18-rc1~124^2~42). It createdbpf_jit_emit_exit_insn()with the '!exit_addr' early-out and replaced the previous unconditional one-instruction emission:- if (i != flen - 1)
-PPC_JMP(exit_addr);
+ if (i != flen - 1) {
+ ret =bpf_jit_emit_exit_insn(image, ctx, b2p[TMP_REG_1], exit_addr);Before
0ffdbce6f4a8a non-finalBPF_EXITalways cost exactly one instruction in every pass, so the sizing pass could not under-count and no overflow was possible. After it, the sizing pass emitsPPC_JMP(0)(1 instruction) while later passes may emit a full inline epilogue, which is precisely the under-count described in this commit message.The commit
d243b62b7bd3only enlarged the epilogue (it addedbpf_jit_build_fentry_stubs()), making an already-existing overflow bigger.Citing the ppc64-trampoline commit also wrongly implies PPC32 is unaffected. The commit
0ffdbce6f4a8made the identical change toarch/powerpc/net/bpf_jit_comp32.c, so 32-bit powerpc has the same bug over the same range.
Quoted 9 lines
A tag survey confirms the range: '
alt_exit_addr' has 0 occurrences inarch/powerpc/net/at v5.16 and v5.17, and 5 occurrences at v5.18, v5.19, v6.0, v6.1 and v6.6.Should the tag be:
Fixes 0ffdbce6f4a8 ("powerpc/bpf: Handle large branch ranges with BPF_EXIT")
True. The current fixes tag is not correct.
Quoted 63 lines
Quoted 6 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0@@ -99,11 +99,10 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)arch/powerpc/net/bpf_jit_comp.c void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx) · line 105
Quoted 10 lines
arch/powerpc/net/bpf_jit_comp.c +3
-4int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr) { -if (!exit_addr ||is_offset_in_branch_range (exit_addr - (ctx->idx *4 ))) { +if (exit_addr &&is_offset_in_branch_range (exit_addr - (long )(ctx->idx *4 ))) {PPC_JMP (exit_addr); - }else if (ctx->alt_exit_addr) { -if (WARN_ON (!is_offset_in_branch_range ((long )ctx->alt_exit_addr - (ctx->idx *4 )))) -return -1 ; + }else if (ctx->alt_exit_addr &&is_offset_in_branch_range ( + (long )(ctx->alt_exit_addr) - (long )(ctx->idx *4 ))) {^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^arch/powerpc/net/bpf_jit_comp.c void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx) · line 106
Quoted 1 line
arch/powerpc/net/bpf_jit_comp.c +0
-0PPC_JMP (ctx->alt_exit_addr);With
alt_exit_addrdefined as unsigned int, does the expression (long)(ctx->alt_exit_addr) - (long)(ctx->idx * 4) produce a different result than (long)ctx->alt_exit_addr - (ctx->idx* 4) due to how integer promotion works?In v7 review, Hari Bathini pointed out that the cast placement may affect integer promotion semantics and requested restoring the earlier syntax with the cast only on
alt_exit_addr:https://lore.kernel.org/bpf/[email protected]
While the current code has passed testing, the concern about integer promotion semantics was never explicitly addressed.
[ ... ]
arch/powerpc/net/bpf_jit_comp.c int bpf_add_extable_entry(struct bpf_prog *fp, u32 *image, u32 *fimage, int pass · line 421
Quoted 7 lines
arch/powerpc/net/bpf_jit_comp.c +1
-1@@ -399,7 +417,7 @@ int bpf_add_extable_entry(struct bpf_prog *fp, u32 *image, u32 *fimage, int passu32 *fixup;/* Populate extable entries only in the last pass */ -if (pass !=2 ) +if (pass <CODEGEN_MIN_PASSES )return 0 ;Does the comment still match the code?
With the old 'if (pass != 2)' and a fixed two-pass loop, pass 2 was the last pass, so the comment was accurate. With the new loop and 'pass <
CODEGEN_MIN_PASSES', both pass 2 and pass 3 can write extable entries, so entries are populated in every pass from 2 onwards, not only in the last one.Also,
CODEGEN_MIN_PASSESis being reused here with a different meaning. Inarch/powerpc/net/bpf_jit.hit documents the minimum number of code-generation passes, but here it is used as 'the first pass in which extable entries are valid'. The two are logically independent - bumpingCODEGEN_MIN_PASSESto 3 to force more settling iterations would silently stop extable entries being written in pass 2.---
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.mdCI run summary: https://github.com/kernel-patches/bpf/actions/runs/33368376983
Re: [PATCH v13 3/8] powerpc/bpf: fix buffer overflow in JIT for large BPF programs
In reply to Saket Kumar BhaskarQuoted 37 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
During size calculation in pass-0,
exit_addris 0 since addrs[fp->len] is not yet populated.bpf_jit_emit_exit_insn()treats a zeroexit_addras in-range and skipsbpf_jit_build_epilogue(), so the alternate inline epilogue instructions are not counted in alloclen.In later passes, if the real
exit_addrfalls outside the 32MB branch range, the full inline epilogue is emitted into the already-allocated buffer, writing past its end and corrupting adjacent memory.Fix by ensuring
exit_addris non-zero before treating it as in-range, so pass-0 always falls through tobpf_jit_build_epilogue()and conservatively accounts for all epilogue instructions in alloclen. Also range checkalt_exit_addrdirectly in the else-if condition.Since
exit_addrhandling now falls through to the epilogue, two related issues inbpf_int_jit_compile()must also be addressed:1. Reset cgctx.alt_exit_addr before the second size-calculation pass. Without this, a stale
alt_exit_addrfrom the first pass causes the second pass to emit a single jump instead of the full epilogue, undercounting alloclen and reintroducing the overflow.2. Recompute addrs[fp->len] at the end of each code-generation pass. The larger pass-0 body can shrink in later passes as out-of-range exits settle into in-range jumps; a stale addrs[fp->len] would leave exit branches targeting past the real (shrunken) epilogue.
Because shrinkage in a later pass can move the epilogue offset, the fixed two-pass loop is no longer sufficient: an exit that was out of range in an earlier pass may fall in range once the epilogue offset shrinks, shrinking the body further and overwriting the start of the epilogue. Convert the code-generation loop to iterate until the program size converges, bounded by
CODEGEN_MAX_PASSES, and fail the JIT if it does not converge.
Quoted 9 lines
Reported-by [email protected]Closes https://lore.kernel.org/bpf/[email protected]/T/#mfcb23909d977b949727cca4f59ee56a13fd69b92 open hereFixes d243b62b7bd3 ("powerpc64/bpf: Add support for bpf trampolines")Signed-off-by Hari Bathini <[email protected]>Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 103 lines
--- arch/powerpc/net/bpf_jit.h | 7 +++++++ arch/powerpc/net/bpf_jit_comp.c | 34 +++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-)arch/powerpc/net/bpf_jit.h +7
-0@@ -14,6 +14,13 @@#include <asm/ppc-opcode.h> #include <linux/build_bug.h> +/* +* We need at least 2 passes for proper code generation, and may need +* additional passes if code size changes between passes. +*/ +#define CODEGEN_MIN_PASSES 2 +#define CODEGEN_MAX_PASSES 3 +#ifdef CONFIG_PPC64_ELF_ABI_V1 #define FUNCTION_DESCR_SIZE 24#else arch/powerpc/net/bpf_jit_comp.c +26
-8@@ -99,11 +99,10 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr) { -if (!exit_addr ||is_offset_in_branch_range (exit_addr - (ctx->idx *4 ))) { +if (exit_addr &&is_offset_in_branch_range (exit_addr - (long )(ctx->idx *4 ))) {PPC_JMP (exit_addr); - }else if (ctx->alt_exit_addr) { -if (WARN_ON (!is_offset_in_branch_range ((long )ctx->alt_exit_addr - (ctx->idx *4 )))) -return -1 ; + }else if (ctx->alt_exit_addr &&is_offset_in_branch_range ( + (long )(ctx->alt_exit_addr) - (long )(ctx->idx *4 ))) {PPC_JMP (ctx->alt_exit_addr); }else { ctx->alt_exit_addr = ctx->idx *4 ;@@ -274,6 +273,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr*/ if (cgctx.seen &SEEN_TAILCALL || !is_offset_in_branch_range ((long )cgctx.idx *4 )) { cgctx.idx =0 ; + cgctx.alt_exit_addr =0 ;if (bpf_jit_build_body (fp,NULL ,NULL , &cgctx, addrs,0 , false)) goto out_err; }@@ -306,10 +306,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr code_base = (u32 *)(image +FUNCTION_DESCR_SIZE ); fcode_base = (u32 *)(fimage +FUNCTION_DESCR_SIZE ); -/* Code generation passes 1-2 */ -for (pass =1 ; pass <3 ; pass++) { +/* Code generation passes 1-2+, loop until program size converges. */ +for (pass =1 ; pass <=CODEGEN_MAX_PASSES ; pass++) { +u32 prev_proglen = proglen; +/* Now build the prologue, body code & epilogue for real. */ cgctx.idx =0 ; + cgctx.exentry_idx =0 ; cgctx.alt_exit_addr =0 ;bpf_jit_build_prologue (code_base, &cgctx); if (bpf_jit_build_body(fp, code_base, fcode_base, &cgctx, addrs, pass,@@ -318,11 +321,26 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prbpf_jit_binary_pack_free (fhdr, hdr); goto out_err; } + addrs[fp->len] = cgctx.idx *4 ;bpf_jit_build_epilogue (code_base, &cgctx); + proglen = cgctx.idx *4 ; +if (bpf_jit_enable >1 )pr_info ("Pass %d: shrink = %d, seen = 0x%x\n" , pass, - proglen - (cgctx.idx *4 ), cgctx.seen); + prev_proglen - proglen, cgctx.seen); + +/* Check if program size has converged, but ensure minimum passes */ +if (pass >=CODEGEN_MIN_PASSES && proglen == prev_proglen) +break ; + +if (pass ==CODEGEN_MAX_PASSES && proglen != prev_proglen) { +pr_err ("BPF JIT: Program did not converge after %d passes\n" , +CODEGEN_MAX_PASSES ); +bpf_arch_text_copy (&fhdr->size, &hdr->size,sizeof (hdr->size)); +bpf_jit_binary_pack_free (fhdr, hdr); + goto out_err; + } }if (bpf_jit_enable >1 )@@ -399,7 +417,7 @@ int bpf_add_extable_entry(struct bpf_prog *fp, u32 *image, u32 *fimage, int passu32 *fixup;/* Populate extable entries only in the last pass */ -if (pass !=2 ) +if (pass <CODEGEN_MIN_PASSES )return 0 ; if (!fp->aux->extable ||
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry
PATCH v13 4/8 powerpc/bpf: fix alignment of long branch trampoline address
In reply to Saket Kumar BhaskarFrom: Abhishek Dubey <[email protected]>
Ensure the dummy trampoline address field present between the OOL stub and the long branch stub is 4/8-byte aligned, for memory compatibility when content loaded to a register.
--- arch/powerpc/net/bpf_jit.h | 7 +++--- arch/powerpc/net/bpf_jit_comp.c | 38 ++++++++++++++++++++++++++----- arch/powerpc/net/bpf_jit_comp32.c | 7 +++--- arch/powerpc/net/bpf_jit_comp64.c | 7 +++--- 4 files changed, 44 insertions(+), 15 deletions(-)
arch/powerpc/net/bpf_jit.h +4 -3
@@ -227,10 +227,11 @@ int bpf_jit_emit_func_call_rel(u32 *image, u32 *fimage, struct codegen_context *int bpf_jit_build_body (struct bpf_prog *fp,u32 *image,u32 *fimage,struct codegen_context *ctx,u32 *addrs,int pass,bool extra_pass);void bpf_jit_build_prologue (u32 *image,struct codegen_context *ctx); -void bpf_jit_build_epilogue (u32 *image,struct codegen_context *ctx); -void bpf_jit_build_fentry_stubs (u32 *image,struct codegen_context *ctx); +void bpf_jit_build_epilogue (u32 *image,u32 *fimage,struct codegen_context *ctx); +void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx);void bpf_jit_realloc_regs (struct codegen_context *ctx); -int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr); +int bpf_jit_emit_exit_insn (u32 *image,u32 *fimage,struct codegen_context *ctx,int tmp_reg, +long exit_addr);void prepare_for_fsession_fentry (u32 *image,struct codegen_context *ctx,int cookie_cnt,int cookie_off,int retval_off);void store_func_meta (u32 *image,struct codegen_context *ctx,u64 func_meta,int func_meta_off);
arch/powerpc/net/bpf_jit_comp.c +32 -6
@@ -49,11 +49,35 @@ asm (" .popsection ;" ); -void bpf_jit_build_fentry_stubs (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx) {int ool_stub_idx, long_branch_stub_idx; +int ool_stub_sz;/* +* Align the mis-aligned dummy_tramp_addr field in the fimage. +* The alignment NOP must appear before OOL stub, to make +* ool_stub_idx & long_branch_stub_idx constant from end. +* +* The fimage can be non 8-byte aligned, so final alignment depends +* on start of fimage and the stub's instruction count offset. The +* OOL stub size is 4 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) +* or 3 instructions (without) before dummy_tramp_addr. +* +* Emit a NOP here if address is not SZL aligned. +* +* In pass=0 when image==NULL, conservatively account for space +* required to accommodate alignment NOP. In case final pass skips +* emitting alignment NOP, the image buffer have 4 spare bytes and +* jited_len signifies correct program size. +*/ + + ool_stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?16 :12 ; +if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + ool_stub_sz,SZL )) +EMIT (PPC_RAW_NOP ()); + +/* +* nop // optional, for alignment of dummy_tramp_addr * Out-of-line stub: * mflr r0 * [b|bl] tramp
@@ -70,7 +94,7 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)/* * Long branch stub: -* .long <dummy_tramp_addr> +* .long <dummy_tramp_addr> // 8-byte aligned * mflr r11 * bcl 20,31,$+4 * mflr r12
@@ -81,6 +105,7 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)*/ if (image) *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; + ctx->idx +=SZL /4 ; long_branch_stub_idx = ctx->idx;EMIT (PPC_RAW_MFLR (_R11));
@@ -97,7 +122,8 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx) } } -int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr) +int bpf_jit_emit_exit_insn (u32 *image,u32 *fimage,struct codegen_context *ctx, +int tmp_reg,long exit_addr) {if (exit_addr &&is_offset_in_branch_range (exit_addr - (long )(ctx->idx *4 ))) {PPC_JMP (exit_addr);
@@ -106,7 +132,7 @@ int bpf_jit_emit_exit_insn(u32 *image, struct codegen_context *ctx, int tmp_reg,PPC_JMP (ctx->alt_exit_addr); }else { ctx->alt_exit_addr = ctx->idx *4 ; -bpf_jit_build_epilogue (image, ctx); +bpf_jit_build_epilogue (image, fimage, ctx); }return 0 ;
@@ -286,7 +312,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr*/ bpf_jit_build_prologue (NULL , &cgctx); addrs[fp->len] = cgctx.idx *4 ; -bpf_jit_build_epilogue (NULL , &cgctx); +bpf_jit_build_epilogue (NULL ,NULL , &cgctx); fixup_len = fp->aux->num_exentries *BPF_FIXUP_LEN *4 ; extable_len = fp->aux->num_exentries *sizeof (struct exception_table_entry );
@@ -322,7 +348,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr goto out_err; } addrs[fp->len] = cgctx.idx *4 ; -bpf_jit_build_epilogue (code_base, &cgctx); +bpf_jit_build_epilogue (code_base, fcode_base, &cgctx); proglen = cgctx.idx *4 ;
arch/powerpc/net/bpf_jit_comp32.c +4 -3
@@ -229,7 +229,7 @@ static void bpf_jit_emit_common_epilogue(u32 *image, struct codegen_context *ctx } -void bpf_jit_build_epilogue (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_epilogue (u32 *image,u32 *fimage,struct codegen_context *ctx) {EMIT (PPC_RAW_MR (_R3,bpf_to_ppc (BPF_REG_0 )));
@@ -237,7 +237,7 @@ void bpf_jit_build_epilogue(u32 *image, struct codegen_context *ctx)EMIT (PPC_RAW_BLR ()); -bpf_jit_build_fentry_stubs (image, ctx); +bpf_jit_build_fentry_stubs (image, fimage, ctx); }/* Relative offset needs to be calculated based on final image location */
@@ -1149,7 +1149,8 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code* we'll just fall through to the epilogue. */ if (i != flen -1 ) { - ret =bpf_jit_emit_exit_insn (image, ctx, _R0, exit_addr); + ret =bpf_jit_emit_exit_insn (image, fimage, + ctx, _R0, exit_addr);if (ret)return ret; }
arch/powerpc/net/bpf_jit_comp64.c +4 -3
@@ -398,7 +398,7 @@ static void bpf_jit_emit_common_epilogue(u32 *image, struct codegen_context *ctx } } -void bpf_jit_build_epilogue (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_epilogue (u32 *image,u32 *fimage,struct codegen_context *ctx) {bpf_jit_emit_common_epilogue (image, ctx);
@@ -407,7 +407,7 @@ void bpf_jit_build_epilogue(u32 *image, struct codegen_context *ctx)EMIT (PPC_RAW_BLR ()); -bpf_jit_build_fentry_stubs (image, ctx); +bpf_jit_build_fentry_stubs (image, fimage, ctx); }/*
@@ -1737,7 +1737,8 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code* we'll just fall through to the epilogue. */ if (i != flen -1 ) { - ret =bpf_jit_emit_exit_insn (image, ctx, tmp1_reg, exit_addr); + ret =bpf_jit_emit_exit_insn (image, fimage, ctx, + tmp1_reg, exit_addr);if (ret)return ret; }
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v13 4/8] powerpc/bpf: fix alignment of long branch trampoline address
Can the conditional alignment NOP here mask program length convergence?
Quoted 4 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0
[ ... ]
Quoted 21 lines
arch/powerpc/net/bpf_jit_comp.c +12
-1@@ -49,11 +49,35 @@ asm (" .popsection ;" ); -void bpf_jit_build_fentry_stubs (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx) {int ool_stub_idx, long_branch_stub_idx; +int ool_stub_sz;/* +* Align the mis-aligned dummy_tramp_addr field in the fimage. +* The alignment NOP must appear before OOL stub, to make +* ool_stub_idx & long_branch_stub_idx constant from end. +* +* The fimage can be non 8-byte aligned, so final alignment depends +* on start of fimage and the stub's instruction count offset. The +* OOL stub size is 4 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) +* or 3 instructions (without) before dummy_tramp_addr. +* +* Emit a NOP here if address is not SZL aligned.
arch/powerpc/net/bpf_jit_comp.c asm ( · line 77
Quoted 10 lines
arch/powerpc/net/bpf_jit_comp.c +10
-0+* +* In pass=0 when image==NULL, conservatively account for space +* required to accommodate alignment NOP. In case final pass skips +* emitting alignment NOP, the image buffer have 4 spare bytes and +* jited_len signifies correct program size. +*/ + + ool_stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?16 :12 ; +if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + ool_stub_sz,SZL )) +EMIT (PPC_RAW_NOP ());
Can the conditional alignment NOP here mask program length convergence?
The preceding commit (5175364d6174 "powerpc/bpf: fix buffer overflow in JIT for large BPF programs") added a convergence check:
if (pass >= CODEGEN_MIN_PASSES && proglen == prev_proglen)
break;to bpf_int_jit_compile(). But the alignment decision above forces the stub block to end at a fixed residue mod 8: the field address (fimage + ctx->idx*4 + ool_stub_sz) is aligned to SZL, and exactly SZL/4 + 7 instructions follow it. So fimage + proglen is congruent to a constant mod 8 every pass, meaning proglen can only change in multiples of 8.
When the body shrinks by an odd multiple of 4 bytes between passes, the NOP can absorb it and keep proglen unchanged. The body can shrink when an exit goes from out-of-range (emitting a full epilogue) to in-range (emitting a single branch):
arch/powerpc/net/bpf_jit_comp.c:bpf_jit_emit_exit_insn() {
if (exit_addr && is_offset_in_branch_range(...)) {
PPC_JMP(exit_addr); // 1 instruction
} else {
...
bpf_jit_build_epilogue(image, fimage, ctx); // N instructions
}
}The shrink is (N-1)*4 bytes. When N-1 is odd, this is 4 mod 8. The NOP appearance or disappearance compensates, making proglen identical across passes even though addrs[] shifted.
Because forward branches use addrs[] from the previous pass (addrs[j] for target j > current i is computed in the prior pass), those branches would land (N-1)*4 bytes past the intended target.
The comment at arch/powerpc/net/bpf_jit.h:132-138 documents that the PowerPC JIT avoids pass-to-pass size changes by padding the short branch case with a NOP, specifically to prevent this scenario. Does reintroducing an address-dependent, pass-varying size risk incorrect branches?
(Note: commit 0cd8bd7da278 later in this series reworks the stub layout and is described as a layout improvement rather than a fix for this commit, which may provide additional context.)
---
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/33368376983
Hari Bathini
maintainerRe: [PATCH v13 4/8] powerpc/bpf: fix alignment of long branch trampoline address
Quoted 75 lines · On 31/08/26 1:42 pm, [email protected] wrote:
Quoted 4 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0[ ... ]
Quoted 21 lines
arch/powerpc/net/bpf_jit_comp.c +12
-1@@ -49,11 +49,35 @@ asm (" .popsection ;" ); -void bpf_jit_build_fentry_stubs (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx) {int ool_stub_idx, long_branch_stub_idx; +int ool_stub_sz;/* +* Align the mis-aligned dummy_tramp_addr field in the fimage. +* The alignment NOP must appear before OOL stub, to make +* ool_stub_idx & long_branch_stub_idx constant from end. +* +* The fimage can be non 8-byte aligned, so final alignment depends +* on start of fimage and the stub's instruction count offset. The +* OOL stub size is 4 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) +* or 3 instructions (without) before dummy_tramp_addr. +* +* Emit a NOP here if address is not SZL aligned. arch/powerpc/net/bpf_jit_comp.c asm ( · line 77
Quoted 10 lines
arch/powerpc/net/bpf_jit_comp.c +10
-0+* +* In pass=0 when image==NULL, conservatively account for space +* required to accommodate alignment NOP. In case final pass skips +* emitting alignment NOP, the image buffer have 4 spare bytes and +* jited_len signifies correct program size. +*/ + + ool_stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?16 :12 ; +if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + ool_stub_sz,SZL )) +EMIT (PPC_RAW_NOP ());Can the conditional alignment NOP here mask program length convergence?
The preceding commit (
5175364d6174"powerpc/bpf: fix buffer overflow in JIT for large BPF programs") added a convergence check:if (pass >= CODEGEN_MIN_PASSES && proglen == prev_proglen) break;to
bpf_int_jit_compile(). But the alignment decision above forces the stub block to end at a fixed residue mod 8: the field address (fimage + ctx->idx*4 +ool_stub_sz) is aligned to SZL, and exactly SZL/4 + 7 instructions follow it. So fimage + proglen is congruent to a constant mod 8 every pass, meaning proglen can only change in multiples of 8.When the body shrinks by an odd multiple of 4 bytes between passes, the NOP can absorb it and keep proglen unchanged. The body can shrink when an exit goes from out-of-range (emitting a full epilogue) to in-range (emitting a single branch):
arch/powerpc/net/bpf_jit_comp.c:bpf_jit_emit_exit_insn() { if (exit_addr && is_offset_in_branch_range(...)) { PPC_JMP(exit_addr); // 1 instruction } else { ... bpf_jit_build_epilogue(image, fimage, ctx); // N instructions } }The shrink is (N-1)*4 bytes. When N-1 is odd, this is 4 mod 8. The NOP appearance or disappearance compensates, making proglen identical across passes even though addrs[] shifted.
Because forward branches use addrs[] from the previous pass (addrs[j] for target j > current i is computed in the prior pass), those branches would land (N-1)*4 bytes past the intended target.
Quoted 9 lines
The comment at arch/powerpc/net/bpf_jit.h:132-138 documents that the PowerPC JIT avoids pass-to-pass size changes by padding the short branch case with a NOP, specifically to prevent this scenario. Does reintroducing an address-dependent, pass-varying size risk incorrect branches?
(Note: commit
0cd8bd7da278later in this series reworks the stub layout and is described as a layout improvement rather than a fix for this commit, which may provide additional context.)
The review correctly identifies a potential mechanism by which a 4-byte alignment NOP could mask a size change. However, that mechanism only causes a convergence failure if the total code-size reduction between passes is exactly compensated by the NOP (i.e. effectively only 4 bytes). In this JIT, changing an exit from the inline epilogue to a branch removes the entire epilogue, so the size reduction is much larger than 4 bytes. The NOP can only compensate for 4 bytes; it cannot hide the remaining reduction. Therefore the proposed convergence failure does not apply to this code.
- Hari
Re: [PATCH v13 4/8] powerpc/bpf: fix alignment of long branch trampoline address
Quoted 91 lines · Hari Bathini
Quoted 79 lines · On 31/08/26 1:42 pm, [email protected] wrote:
Quoted 4 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0[ ... ]
Quoted 27 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0@@ -49,11 +49,35 @@ asm (" .popsection ;"
);-void
bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)
+voidbpf_jit_build_fentry_stubs(u32 *image, u32 *fimage, struct codegen_context *ctx)
{
intool_stub_idx,long_branch_stub_idx;
+ intool_stub_sz;/*
+ * Align the mis-aligneddummy_tramp_addrfield in the fimage. + * The alignment NOP must appear before OOL stub, to make + *ool_stub_idx&long_branch_stub_idxconstant from end. + *
+ * The fimage can be non 8-byte aligned, so final alignment depends + * on start of fimage and the stub's instruction count offset. The + * OOL stub size is 4 instructions (withCONFIG_PPC_FTRACE_OUT_OF_LINE)
+ * or 3 instructions (without) beforedummy_tramp_addr. + *
+ * Emit a NOP here if address is not SZL aligned.
+ *
+ * In pass=0 when image==NULL, conservatively account for space + * required to accommodate alignment NOP. In case final pass skipsQuoted 8 lines
+ * emitting alignment NOP, the image buffer have 4 spare bytes and + *
jited_lensignifies correct program size.
+ */
+
+ool_stub_sz=IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE)? 16 : 12; + if (!image || !IS_ALIGNED((unsigned long)fimage + ctx->idx*4 +ool_stub_sz, SZL))
+ EMIT(PPC_RAW_NOP());Can the conditional alignment NOP here mask program length convergence?
The preceding commit (
5175364d6174"powerpc/bpf: fix buffer overflow in JIT for large BPF programs") added a convergence check:if (pass >=
CODEGEN_MIN_PASSES&& proglen ==prev_proglen) break;to
bpf_int_jit_compile(). But the alignment decision above forces the stub block to end at a fixed residue mod 8: the field address (fimage + ctx->idx*4 +ool_stub_sz) is aligned to SZL, and exactly SZL/4 + 7 instructions follow it. So fimage + proglen is congruent to a constant mod 8 every pass, meaning proglen can only change in multiples of 8.When the body shrinks by an odd multiple of 4 bytes between passes, the NOP can absorb it and keep proglen unchanged. The body can shrink when an exit goes from out-of-range (emitting a full epilogue) to in-range (emitting a single branch):
arch/powerpc/net/bpf_jit_comp.c:bpf_jit_emit_exit_insn() { if (
exit_addr&&is_offset_in_branch_range(...)) { PPC_JMP(exit_addr); // 1 instruction } else {
...
bpf_jit_build_epilogue(image, fimage, ctx); // N instructions }
}The shrink is (N-1)*4 bytes. When N-1 is odd, this is 4 mod 8. The NOP appearance or disappearance compensates, making proglen identical across passes even though addrs[] shifted.
Because forward branches use addrs[] from the previous pass (addrs[j] for target j > current i is computed in the prior pass), those branches would land (N-1)*4 bytes past the intended target.
Quoted 9 lines
The comment at arch/powerpc/net/bpf_jit.h:132-138 documents that the PowerPC JIT avoids pass-to-pass size changes by padding the short branch case with a NOP, specifically to prevent this scenario. Does reintroducing an address-dependent, pass-varying size risk incorrect branches?
(Note: commit
0cd8bd7da278later in this series reworks the stub layout and is described as a layout improvement rather than a fix for this commit, which may provide additional context.)
Quoted 9 lines
The review correctly identifies a potential mechanism by which a 4-byte alignment NOP could mask a size change. However, that mechanism only causes a convergence failure if the total code-size reduction between passes is exactly compensated by the NOP (i.e. effectively only 4 bytes). In this JIT, changing an exit from the inline epilogue to a branch removes the entire epilogue, so the size reduction is much larger than 4 bytes. The NOP can only compensate for 4 bytes; it cannot hide the remaining reduction. Therefore the proposed convergence failure does not apply to this code.
With the above said:
Re: [PATCH v13 4/8] powerpc/bpf: fix alignment of long branch trampoline address
In reply to Saket Kumar BhaskarQuoted 6 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
Ensure the dummy trampoline address field present between the OOL stub and the long branch stub is 4/8-byte aligned, for memory compatibility when content loaded to a register.
Quoted 6 lines
Reported-by Hari Bathini <[email protected]>Fixes d243b62b7bd3 ("powerpc64/bpf: Add support for bpf trampolines")Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 185 lines
--- arch/powerpc/net/bpf_jit.h | 7 +++--- arch/powerpc/net/bpf_jit_comp.c | 38 ++++++++++++++++++++++++++----- arch/powerpc/net/bpf_jit_comp32.c | 7 +++--- arch/powerpc/net/bpf_jit_comp64.c | 7 +++--- 4 files changed, 44 insertions(+), 15 deletions(-)arch/powerpc/net/bpf_jit.h +4
-3@@ -227,10 +227,11 @@ int bpf_jit_emit_func_call_rel(u32 *image, u32 *fimage, struct codegen_context *int bpf_jit_build_body (struct bpf_prog *fp,u32 *image,u32 *fimage,struct codegen_context *ctx,u32 *addrs,int pass,bool extra_pass);void bpf_jit_build_prologue (u32 *image,struct codegen_context *ctx); -void bpf_jit_build_epilogue (u32 *image,struct codegen_context *ctx); -void bpf_jit_build_fentry_stubs (u32 *image,struct codegen_context *ctx); +void bpf_jit_build_epilogue (u32 *image,u32 *fimage,struct codegen_context *ctx); +void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx);void bpf_jit_realloc_regs (struct codegen_context *ctx); -int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr); +int bpf_jit_emit_exit_insn (u32 *image,u32 *fimage,struct codegen_context *ctx,int tmp_reg, +long exit_addr);void prepare_for_fsession_fentry (u32 *image,struct codegen_context *ctx,int cookie_cnt,int cookie_off,int retval_off);void store_func_meta (u32 *image,struct codegen_context *ctx,u64 func_meta,int func_meta_off);arch/powerpc/net/bpf_jit_comp.c +32
-6@@ -49,11 +49,35 @@ asm (" .popsection ;" ); -void bpf_jit_build_fentry_stubs (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx) {int ool_stub_idx, long_branch_stub_idx; +int ool_stub_sz;/* +* Align the mis-aligned dummy_tramp_addr field in the fimage. +* The alignment NOP must appear before OOL stub, to make +* ool_stub_idx & long_branch_stub_idx constant from end. +* +* The fimage can be non 8-byte aligned, so final alignment depends +* on start of fimage and the stub's instruction count offset. The +* OOL stub size is 4 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) +* or 3 instructions (without) before dummy_tramp_addr. +* +* Emit a NOP here if address is not SZL aligned. +* +* In pass=0 when image==NULL, conservatively account for space +* required to accommodate alignment NOP. In case final pass skips +* emitting alignment NOP, the image buffer have 4 spare bytes and +* jited_len signifies correct program size. +*/ + + ool_stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?16 :12 ; +if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + ool_stub_sz,SZL )) +EMIT (PPC_RAW_NOP ()); + +/* +* nop // optional, for alignment of dummy_tramp_addr * Out-of-line stub: * mflr r0 * [b|bl] tramp @@ -70,7 +94,7 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)/* * Long branch stub: -* .long <dummy_tramp_addr> +* .long <dummy_tramp_addr> // 8-byte aligned * mflr r11 * bcl 20,31,$+4 * mflr r12 @@ -81,6 +105,7 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx)*/ if (image) *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; + ctx->idx +=SZL /4 ; long_branch_stub_idx = ctx->idx;EMIT (PPC_RAW_MFLR (_R11));@@ -97,7 +122,8 @@ void bpf_jit_build_fentry_stubs(u32 *image, struct codegen_context *ctx) } } -int bpf_jit_emit_exit_insn (u32 *image,struct codegen_context *ctx,int tmp_reg,long exit_addr) +int bpf_jit_emit_exit_insn (u32 *image,u32 *fimage,struct codegen_context *ctx, +int tmp_reg,long exit_addr) {if (exit_addr &&is_offset_in_branch_range (exit_addr - (long )(ctx->idx *4 ))) {PPC_JMP (exit_addr);@@ -106,7 +132,7 @@ int bpf_jit_emit_exit_insn(u32 *image, struct codegen_context *ctx, int tmp_reg,PPC_JMP (ctx->alt_exit_addr); }else { ctx->alt_exit_addr = ctx->idx *4 ; -bpf_jit_build_epilogue (image, ctx); +bpf_jit_build_epilogue (image, fimage, ctx); }return 0 ;@@ -286,7 +312,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr*/ bpf_jit_build_prologue (NULL , &cgctx); addrs[fp->len] = cgctx.idx *4 ; -bpf_jit_build_epilogue (NULL , &cgctx); +bpf_jit_build_epilogue (NULL ,NULL , &cgctx); fixup_len = fp->aux->num_exentries *BPF_FIXUP_LEN *4 ; extable_len = fp->aux->num_exentries *sizeof (struct exception_table_entry );@@ -322,7 +348,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr goto out_err; } addrs[fp->len] = cgctx.idx *4 ; -bpf_jit_build_epilogue (code_base, &cgctx); +bpf_jit_build_epilogue (code_base, fcode_base, &cgctx); proglen = cgctx.idx *4 ;arch/powerpc/net/bpf_jit_comp32.c +4
-3@@ -229,7 +229,7 @@ static void bpf_jit_emit_common_epilogue(u32 *image, struct codegen_context *ctx } -void bpf_jit_build_epilogue (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_epilogue (u32 *image,u32 *fimage,struct codegen_context *ctx) {EMIT (PPC_RAW_MR (_R3,bpf_to_ppc (BPF_REG_0 )));@@ -237,7 +237,7 @@ void bpf_jit_build_epilogue(u32 *image, struct codegen_context *ctx)EMIT (PPC_RAW_BLR ()); -bpf_jit_build_fentry_stubs (image, ctx); +bpf_jit_build_fentry_stubs (image, fimage, ctx); }/* Relative offset needs to be calculated based on final image location */ @@ -1149,7 +1149,8 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code* we'll just fall through to the epilogue. */ if (i != flen -1 ) { - ret =bpf_jit_emit_exit_insn (image, ctx, _R0, exit_addr); + ret =bpf_jit_emit_exit_insn (image, fimage, + ctx, _R0, exit_addr);if (ret)return ret; }arch/powerpc/net/bpf_jit_comp64.c +4
-3@@ -398,7 +398,7 @@ static void bpf_jit_emit_common_epilogue(u32 *image, struct codegen_context *ctx } } -void bpf_jit_build_epilogue (u32 *image,struct codegen_context *ctx) +void bpf_jit_build_epilogue (u32 *image,u32 *fimage,struct codegen_context *ctx) {bpf_jit_emit_common_epilogue (image, ctx);@@ -407,7 +407,7 @@ void bpf_jit_build_epilogue(u32 *image, struct codegen_context *ctx)EMIT (PPC_RAW_BLR ()); -bpf_jit_build_fentry_stubs (image, ctx); +bpf_jit_build_fentry_stubs (image, fimage, ctx); }/* @@ -1737,7 +1737,8 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code* we'll just fall through to the epilogue. */ if (i != flen -1 ) { - ret =bpf_jit_emit_exit_insn (image, ctx, tmp1_reg, exit_addr); + ret =bpf_jit_emit_exit_insn (image, fimage, ctx, + tmp1_reg, exit_addr);if (ret)return ret; }
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry
PATCH v13 5/8 powerpc/bpf: Move out dummy_tramp_addr after Long branch stub
In reply to Saket Kumar BhaskarFrom: Abhishek Dubey <[email protected]>
Move the long branch address field to the bottom of the long branch stub. This allows uninterrupted disassembly until the last 4/8 bytes. The last bytes exclusion is logically necessary to prevent disassembly failure, otherwise the actual program layout is never altered. Hence no effect on overall program size. Also, align dummy_tramp_addr field with 8-byte boundary.
Following is disassembler output for test program with moved down dummy_tramp_addr field:
.....
.....
pc:68 left:44 a6 03 08 7c : mtlr 0 pc:72 left:40 bc ff ff 4b : b .-68 pc:76 left:36 a6 02 68 7d : mflr 11 pc:80 left:32 05 00 9f 42 : bcl 20, 31, .+4 pc:84 left:28 a6 02 88 7d : mflr 12 pc:88 left:24 14 00 8c e9 : ld 12, 20(12) pc:92 left:20 a6 03 89 7d : mtctr 12 pc:96 left:16 a6 03 68 7d : mtlr 11 pc:100 left:12 20 04 80 4e : bctr pc:104 left:8 c0 34 1d 00 :
Failure log:
Can't disasm instruction at offset 104: c0 34 1d 00 00 00 00 c0 Disassembly logic can truncate at 104, ignoring last 8 bytes.
Update the dummy_tramp_addr field offset calculation from the end of the program to reflect its new location, for bpf_arch_text_poke() to update the actual trampoline's address in this field.
All BPF trampoline selftests continue to pass with this patch applied.
--- arch/powerpc/net/bpf_jit_comp.c | 44 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 20 deletions(-)
arch/powerpc/net/bpf_jit_comp.c +24 -20
@@ -52,19 +52,19 @@ asm (void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx) {int ool_stub_idx, long_branch_stub_idx; -int ool_stub_sz; +int stub_sz;/* +* The dummy_tramp_addr field is placed at bottom of Long branch stub. * Align the mis-aligned dummy_tramp_addr field in the fimage. * The alignment NOP must appear before OOL stub, to make * ool_stub_idx & long_branch_stub_idx constant from end. * * The fimage can be non 8-byte aligned, so final alignment depends -* on start of fimage and the stub's instruction count offset. The -* OOL stub size is 4 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) -* or 3 instructions (without) before dummy_tramp_addr. -* -* Emit a NOP here if address is not SZL aligned. +* on start of fimage and the stub's instruction count. The +* stubs block has 11 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) +* or 10 instructions (without) before dummy_tramp_addr field. Emit a +* NOP if the address of dummy_tramp_addr is non aligned. * * In pass=0 when image==NULL, conservatively account for space * required to accommodate alignment NOP. In case final pass skips
@@ -72,8 +72,8 @@ void bpf_jit_build_fentry_stubs(u32 *image, u32 *fimage, struct codegen_context* jited_len signifies correct program size. */ - ool_stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?16 :12 ; -if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + ool_stub_sz,SZL )) + stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?44 :40 ; +if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + stub_sz,SZL ))EMIT (PPC_RAW_NOP ());/*
@@ -94,28 +94,29 @@ void bpf_jit_build_fentry_stubs(u32 *image, u32 *fimage, struct codegen_context/* * Long branch stub: -* .long <dummy_tramp_addr> // 8-byte aligned * mflr r11 * bcl 20,31,$+4 -* mflr r12 -* ld r12, -8-SZL(r12) +* mflr r12 // lr/r12 stores pc of current(this) inst. +* ld r12, 20(r12) // offset(dummy_tramp_addr) from prev inst. is 20 * mtctr r12 -* mtlr r11 // needed to retain ftrace ABI +* mtlr r11 // needed to retain ftrace ABI * bctr +* .long <dummy_tramp_addr> // SZL bytes aligned */ -if (image) - *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; - - ctx->idx +=SZL /4 ; long_branch_stub_idx = ctx->idx;EMIT (PPC_RAW_MFLR (_R11));EMIT (PPC_RAW_BCL4 ());EMIT (PPC_RAW_MFLR (_R12)); -EMIT (PPC_RAW_LL (_R12, _R12,-8 -SZL )); +EMIT (PPC_RAW_LL (_R12, _R12,20 ));EMIT (PPC_RAW_MTCTR (_R12));EMIT (PPC_RAW_MTLR (_R11));EMIT (PPC_RAW_BCTR ()); +if (image) + *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; + + ctx->idx +=SZL /4 ; +if (!bpf_jit_ool_stub) { bpf_jit_ool_stub = (ctx->idx - ool_stub_idx) *4 ; bpf_jit_long_branch_stub = (ctx->idx - long_branch_stub_idx) *4 ;
@@ -1309,6 +1310,7 @@ static void do_isync(void *info __maybe_unused)* bpf_func: * [nop|b] ool_stub * 2. Out-of-line stub: +* nop // optional nop for alignment * ool_stub: * mflr r0 * [b|bl] <bpf_prog>/<long_branch_stub>
@@ -1316,14 +1318,14 @@ static void do_isync(void *info __maybe_unused)* b bpf_func + 4 * 3. Long branch stub: * long_branch_stub: -* .long <branch_addr>/<dummy_tramp> * mflr r11 * bcl 20,31,$+4 * mflr r12 -* ld r12, -16(r12) +* ld r12, 20(r12) * mtctr r12 * mtlr r11 // needed to retain ftrace ABI * bctr +* .long <branch_addr>/<dummy_tramp> * * dummy_tramp is used to reduce synchronization requirements. *
@@ -1425,10 +1427,12 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t,* 1. Update the address in the long branch stub: * If new_addr is out of range, we will have to use the long branch stub, so patch new_addr * here. Otherwise, revert to dummy_tramp, but only if we had patched old_addr here. +* +* dummy_tramp_addr moved to bottom of long branch stub. */ if ((new_addr && !is_offset_in_branch_range (new_addr - ip)) || (old_addr && !is_offset_in_branch_range (old_addr - ip))) - ret =patch_ulong ((void *)(bpf_func_end - bpf_jit_long_branch_stub -SZL ), + ret =patch_ulong ((void *)(bpf_func_end -SZL ),/* SZL: dummy_tramp_addr offset */ (new_addr && !is_offset_in_branch_range (new_addr - ip)) ? (unsigned long )new_addr : (unsigned long )dummy_tramp);if (ret)
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v13 5/8] powerpc/bpf: Move out dummy_tramp_addr after Long branch stub
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Preexisting boundary asymmetry in branch range validation causes silent failure and NOP patching when trampoline targets are exactly 32MB away. --
commit 5b58234b51abd3de6209e164ca3ba6723fb0f569
Author: Abhishek Dubey <[email protected]> powerpc/bpf: Move out dummy_tramp_addr after Long branch stub
This patch moves the long branch address field to the bottom of the long branch stub to prevent disassembly failure. It updates the dummy_tramp_addr field offset calculation from the end of the program to reflect its new location.
Quoted 4 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0
[ ... ]
Quoted 4 lines
arch/powerpc/net/bpf_jit_comp.c +0
-0@@ -1425,10 +1427,12 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t,* 1. Update the address in the long branch stub: * If new_addr is out of range, we will have to use the long branch stub, so patch new_addr * here. Otherwise, revert to dummy_tramp, but only if we had patched old_addr here.
arch/powerpc/net/bpf_jit_comp.c int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, · line 1438
Quoted 10 lines
arch/powerpc/net/bpf_jit_comp.c +3
-1+* +* dummy_tramp_addr moved to bottom of long branch stub. */ if ((new_addr && !is_offset_in_branch_range (new_addr - ip)) || (old_addr && !is_offset_in_branch_range (old_addr - ip))) - ret =patch_ulong ((void *)(bpf_func_end - bpf_jit_long_branch_stub -SZL ), + ret =patch_ulong ((void *)(bpf_func_end -SZL ),/* SZL: dummy_tramp_addr offset */ (new_addr && !is_offset_in_branch_range (new_addr - ip)) ? (unsigned long )new_addr : (unsigned long )dummy_tramp);if (ret)
[Severity: High]
This is a pre-existing issue, but looking at bpf_arch_text_poke(), there appears to be a boundary asymmetry in the branch range validation just above this hunk.
Earlier in the function, the offset is calculated as ip - old_addr and ip - new_addr:
if (old_addr) {
if (is_offset_in_branch_range(ip - old_addr))
create_branch(&old_inst, ip, (unsigned long)old_addr, branch_flags);
else
...
if (new_addr) {
if (is_offset_in_branch_range(ip - new_addr))
create_branch(&new_inst, ip, (unsigned long)new_addr, branch_flags);
elseWhile in the hunk above it correctly calculates new_addr - ip and old_addr - ip.
Does this cause silent failures when trampoline targets are exactly 32MB away?
If new_addr is exactly 0x2000000 bytes ahead of ip, the true offset (0x2000000) is out of range. However, ip - new_addr evaluates to -0x2000000, which is_offset_in_branch_range() incorrectly sees as IN range.
If this happens, the code enters the block and calls create_branch(), which correctly calculates the true offset, fails its internal bounds check, and returns an error.
Because bpf_arch_text_poke() ignores the return value of create_branch() in this path, does this leave new_inst as a NOP?
If so, bpf_modify_inst() would silently patch a NOP into the execution path instead of a branch to the long branch stub, dropping the BPF call entirely.
Similarly, if the target is 0x2000000 bytes behind ip, the true offset -0x2000000 is valid, but ip - old_addr evaluates to 0x2000000, incorrectly triggering the else path.
Could this mismatch cause bpf_modify_inst() to fail (as the instruction currently in memory is a direct branch, not a stub branch), leaving the system unable to detach the BPF program?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Re: [PATCH v13 5/8] powerpc/bpf: Move out dummy_tramp_addr after Long branch stub
In reply to Saket Kumar BhaskarQuoted 28 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
Move the long branch address field to the bottom of the long branch stub. This allows uninterrupted disassembly until the last 4/8 bytes. The last bytes exclusion is logically necessary to prevent disassembly failure, otherwise the actual program layout is never altered. Hence no effect on overall program size. Also, align
dummy_tramp_addrfield with 8-byte boundary.Following is disassembler output for test program with moved down
dummy_tramp_addrfield:
.....
.....pc:68 left:44 a6 03 08 7c : mtlr 0 pc:72 left:40 bc ff ff 4b : b .-68 pc:76 left:36 a6 02 68 7d : mflr 11 pc:80 left:32 05 00 9f 42 : bcl 20, 31, .+4 pc:84 left:28 a6 02 88 7d : mflr 12 pc:88 left:24 14 00 8c e9 : ld 12, 20(12) pc:92 left:20 a6 03 89 7d : mtctr 12 pc:96 left:16 a6 03 68 7d : mtlr 11 pc:100 left:12 20 04 80 4e : bctr pc:104 left:8 c0 34 1d 00 :Failure log:
Can't disasm instruction at offset 104: c0 34 1d 00 00 00 00 c0 Disassembly logic can truncate at 104, ignoring last 8 bytes.
Quoted 6 lines
Update the
dummy_tramp_addrfield offset calculation from the end of the program to reflect its new location, forbpf_arch_text_poke()to update the actual trampoline's address in this field.All BPF trampoline selftests continue to pass with this patch applied.
This makes it easy to disassemble the bpf programs. Looks good.
Quoted 126 lines
Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>--- arch/powerpc/net/bpf_jit_comp.c | 44 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 20 deletions(-)arch/powerpc/net/bpf_jit_comp.c +24
-20@@ -52,19 +52,19 @@ asm (void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx) {int ool_stub_idx, long_branch_stub_idx; -int ool_stub_sz; +int stub_sz;/* +* The dummy_tramp_addr field is placed at bottom of Long branch stub. * Align the mis-aligned dummy_tramp_addr field in the fimage. * The alignment NOP must appear before OOL stub, to make * ool_stub_idx & long_branch_stub_idx constant from end. * * The fimage can be non 8-byte aligned, so final alignment depends -* on start of fimage and the stub's instruction count offset. The -* OOL stub size is 4 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) -* or 3 instructions (without) before dummy_tramp_addr. -* -* Emit a NOP here if address is not SZL aligned. +* on start of fimage and the stub's instruction count. The +* stubs block has 11 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) +* or 10 instructions (without) before dummy_tramp_addr field. Emit a +* NOP if the address of dummy_tramp_addr is non aligned. * * In pass=0 when image==NULL, conservatively account for space * required to accommodate alignment NOP. In case final pass skips @@ -72,8 +72,8 @@ void bpf_jit_build_fentry_stubs(u32 *image, u32 *fimage, struct codegen_context* jited_len signifies correct program size. */ - ool_stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?16 :12 ; -if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + ool_stub_sz,SZL )) + stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?44 :40 ; +if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + stub_sz,SZL ))EMIT (PPC_RAW_NOP ());/* @@ -94,28 +94,29 @@ void bpf_jit_build_fentry_stubs(u32 *image, u32 *fimage, struct codegen_context/* * Long branch stub: -* .long <dummy_tramp_addr> // 8-byte aligned * mflr r11 * bcl 20,31,$+4 -* mflr r12 -* ld r12, -8-SZL(r12) +* mflr r12 // lr/r12 stores pc of current(this) inst. +* ld r12, 20(r12) // offset(dummy_tramp_addr) from prev inst. is 20 * mtctr r12 -* mtlr r11 // needed to retain ftrace ABI +* mtlr r11 // needed to retain ftrace ABI * bctr +* .long <dummy_tramp_addr> // SZL bytes aligned */ -if (image) - *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; - - ctx->idx +=SZL /4 ; long_branch_stub_idx = ctx->idx;EMIT (PPC_RAW_MFLR (_R11));EMIT (PPC_RAW_BCL4 ());EMIT (PPC_RAW_MFLR (_R12)); -EMIT (PPC_RAW_LL (_R12, _R12,-8 -SZL )); +EMIT (PPC_RAW_LL (_R12, _R12,20 ));EMIT (PPC_RAW_MTCTR (_R12));EMIT (PPC_RAW_MTLR (_R11));EMIT (PPC_RAW_BCTR ()); +if (image) + *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; + + ctx->idx +=SZL /4 ; +if (!bpf_jit_ool_stub) { bpf_jit_ool_stub = (ctx->idx - ool_stub_idx) *4 ; bpf_jit_long_branch_stub = (ctx->idx - long_branch_stub_idx) *4 ;@@ -1309,6 +1310,7 @@ static void do_isync(void *info __maybe_unused)* bpf_func: * [nop|b] ool_stub * 2. Out-of-line stub: +* nop // optional nop for alignment * ool_stub: * mflr r0 * [b|bl] <bpf_prog>/<long_branch_stub> @@ -1316,14 +1318,14 @@ static void do_isync(void *info __maybe_unused)* b bpf_func + 4 * 3. Long branch stub: * long_branch_stub: -* .long <branch_addr>/<dummy_tramp> * mflr r11 * bcl 20,31,$+4 * mflr r12 -* ld r12, -16(r12) +* ld r12, 20(r12) * mtctr r12 * mtlr r11 // needed to retain ftrace ABI * bctr +* .long <branch_addr>/<dummy_tramp> * * dummy_tramp is used to reduce synchronization requirements. * @@ -1425,10 +1427,12 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t,* 1. Update the address in the long branch stub: * If new_addr is out of range, we will have to use the long branch stub, so patch new_addr * here. Otherwise, revert to dummy_tramp, but only if we had patched old_addr here. +* +* dummy_tramp_addr moved to bottom of long branch stub. */ if ((new_addr && !is_offset_in_branch_range (new_addr - ip)) || (old_addr && !is_offset_in_branch_range (old_addr - ip))) - ret =patch_ulong ((void *)(bpf_func_end - bpf_jit_long_branch_stub -SZL ), + ret =patch_ulong ((void *)(bpf_func_end -SZL ),/* SZL: dummy_tramp_addr offset */ (new_addr && !is_offset_in_branch_range (new_addr - ip)) ? (unsigned long )new_addr : (unsigned long )dummy_tramp);if (ret)
Re: [PATCH v13 5/8] powerpc/bpf: Move out dummy_tramp_addr after Long branch stub
In reply to Saket Kumar BhaskarQuoted 32 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
Move the long branch address field to the bottom of the long branch stub. This allows uninterrupted disassembly until the last 4/8 bytes. The last bytes exclusion is logically necessary to prevent disassembly failure, otherwise the actual program layout is never altered. Hence no effect on overall program size. Also, align
dummy_tramp_addrfield with 8-byte boundary.Following is disassembler output for test program with moved down
dummy_tramp_addrfield:
.....
.....pc:68 left:44 a6 03 08 7c : mtlr 0 pc:72 left:40 bc ff ff 4b : b .-68 pc:76 left:36 a6 02 68 7d : mflr 11 pc:80 left:32 05 00 9f 42 : bcl 20, 31, .+4 pc:84 left:28 a6 02 88 7d : mflr 12 pc:88 left:24 14 00 8c e9 : ld 12, 20(12) pc:92 left:20 a6 03 89 7d : mtctr 12 pc:96 left:16 a6 03 68 7d : mtlr 11 pc:100 left:12 20 04 80 4e : bctr pc:104 left:8 c0 34 1d 00 :Failure log:
Can't disasm instruction at offset 104: c0 34 1d 00 00 00 00 c0 Disassembly logic can truncate at 104, ignoring last 8 bytes.Update the
dummy_tramp_addrfield offset calculation from the end of the program to reflect its new location, forbpf_arch_text_poke()to update the actual trampoline's address in this field.
Quoted 5 lines
All BPF trampoline selftests continue to pass with this patch applied.
Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 123 lines
--- arch/powerpc/net/bpf_jit_comp.c | 44 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 20 deletions(-)arch/powerpc/net/bpf_jit_comp.c +24
-20@@ -52,19 +52,19 @@ asm (void bpf_jit_build_fentry_stubs (u32 *image,u32 *fimage,struct codegen_context *ctx) {int ool_stub_idx, long_branch_stub_idx; -int ool_stub_sz; +int stub_sz;/* +* The dummy_tramp_addr field is placed at bottom of Long branch stub. * Align the mis-aligned dummy_tramp_addr field in the fimage. * The alignment NOP must appear before OOL stub, to make * ool_stub_idx & long_branch_stub_idx constant from end. * * The fimage can be non 8-byte aligned, so final alignment depends -* on start of fimage and the stub's instruction count offset. The -* OOL stub size is 4 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) -* or 3 instructions (without) before dummy_tramp_addr. -* -* Emit a NOP here if address is not SZL aligned. +* on start of fimage and the stub's instruction count. The +* stubs block has 11 instructions (with CONFIG_PPC_FTRACE_OUT_OF_LINE) +* or 10 instructions (without) before dummy_tramp_addr field. Emit a +* NOP if the address of dummy_tramp_addr is non aligned. * * In pass=0 when image==NULL, conservatively account for space * required to accommodate alignment NOP. In case final pass skips @@ -72,8 +72,8 @@ void bpf_jit_build_fentry_stubs(u32 *image, u32 *fimage, struct codegen_context* jited_len signifies correct program size. */ - ool_stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?16 :12 ; -if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + ool_stub_sz,SZL )) + stub_sz =IS_ENABLED (CONFIG_PPC_FTRACE_OUT_OF_LINE ) ?44 :40 ; +if (!image || !IS_ALIGNED ((unsigned long )fimage + ctx->idx*4 + stub_sz,SZL ))EMIT (PPC_RAW_NOP ());/* @@ -94,28 +94,29 @@ void bpf_jit_build_fentry_stubs(u32 *image, u32 *fimage, struct codegen_context/* * Long branch stub: -* .long <dummy_tramp_addr> // 8-byte aligned * mflr r11 * bcl 20,31,$+4 -* mflr r12 -* ld r12, -8-SZL(r12) +* mflr r12 // lr/r12 stores pc of current(this) inst. +* ld r12, 20(r12) // offset(dummy_tramp_addr) from prev inst. is 20 * mtctr r12 -* mtlr r11 // needed to retain ftrace ABI +* mtlr r11 // needed to retain ftrace ABI * bctr +* .long <dummy_tramp_addr> // SZL bytes aligned */ -if (image) - *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; - - ctx->idx +=SZL /4 ; long_branch_stub_idx = ctx->idx;EMIT (PPC_RAW_MFLR (_R11));EMIT (PPC_RAW_BCL4 ());EMIT (PPC_RAW_MFLR (_R12)); -EMIT (PPC_RAW_LL (_R12, _R12,-8 -SZL )); +EMIT (PPC_RAW_LL (_R12, _R12,20 ));EMIT (PPC_RAW_MTCTR (_R12));EMIT (PPC_RAW_MTLR (_R11));EMIT (PPC_RAW_BCTR ()); +if (image) + *((unsigned long *)&image[ctx->idx]) = (unsigned long )dummy_tramp; + + ctx->idx +=SZL /4 ; +if (!bpf_jit_ool_stub) { bpf_jit_ool_stub = (ctx->idx - ool_stub_idx) *4 ; bpf_jit_long_branch_stub = (ctx->idx - long_branch_stub_idx) *4 ;@@ -1309,6 +1310,7 @@ static void do_isync(void *info __maybe_unused)* bpf_func: * [nop|b] ool_stub * 2. Out-of-line stub: +* nop // optional nop for alignment * ool_stub: * mflr r0 * [b|bl] <bpf_prog>/<long_branch_stub> @@ -1316,14 +1318,14 @@ static void do_isync(void *info __maybe_unused)* b bpf_func + 4 * 3. Long branch stub: * long_branch_stub: -* .long <branch_addr>/<dummy_tramp> * mflr r11 * bcl 20,31,$+4 * mflr r12 -* ld r12, -16(r12) +* ld r12, 20(r12) * mtctr r12 * mtlr r11 // needed to retain ftrace ABI * bctr +* .long <branch_addr>/<dummy_tramp> * * dummy_tramp is used to reduce synchronization requirements. * @@ -1425,10 +1427,12 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t,* 1. Update the address in the long branch stub: * If new_addr is out of range, we will have to use the long branch stub, so patch new_addr * here. Otherwise, revert to dummy_tramp, but only if we had patched old_addr here. +* +* dummy_tramp_addr moved to bottom of long branch stub. */ if ((new_addr && !is_offset_in_branch_range (new_addr - ip)) || (old_addr && !is_offset_in_branch_range (old_addr - ip))) - ret =patch_ulong ((void *)(bpf_func_end - bpf_jit_long_branch_stub -SZL ), + ret =patch_ulong ((void *)(bpf_func_end -SZL ),/* SZL: dummy_tramp_addr offset */ (new_addr && !is_offset_in_branch_range (new_addr - ip)) ? (unsigned long )new_addr : (unsigned long )dummy_tramp);if (ret)
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry
PATCH v13 6/8 selftests/bpf: Fix powerpc JIT disassembly failure
In reply to Saket Kumar BhaskarFrom: Abhishek Dubey <[email protected]>
Ensure that the trampoline stubs JITed at the tail of the epilogue do not expose the dummy trampoline address stored in the last 8 bytes(64-bit) and last 4 bytes(32-bit) to the disassembly flow. Prevent the disassembler from ingesting this memory address, as it may occasionally decode into a seemingly valid but incorrect instruction. Fix this issue by truncating the last 8/4 bytes from JITed buffers before supplying them for disassembly.
--- .../selftests/bpf/jit_disasm_helpers.c | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-)
tools/testing/selftests/bpf/jit_disasm_helpers.c +24 -1
@@ -179,9 +179,11 @@ int get_jited_program_text(int fd, char *text, size_t text_sz)struct bpf_prog_info info = {};__u32 info_len =sizeof (info);__u32 jited_funcs, len, pc; +__u32 trunc_len =0 , disasm_len;__u32 *func_lens =NULL ;FILE *text_out =NULL ;uint8_t *image =NULL ; +char *triple =NULL ;int i, err =0 ;if (!llvm_initialized) {
@@ -225,9 +227,30 @@ int get_jited_program_text(int fd, char *text, size_t text_sz)if (!ASSERT_OK (err,"bpf_prog_get_info_by_fd #2" )) goto out; +/* +* last 8 bytes contains dummy_trampoline address in JIT +* output on 64-bit and last 4 bytes on 32-bit powerpc, +* which can't disassemble to a valid instruction. +*/ + triple =LLVMGetDefaultTargetTriple (); +if (triple) { +if (strstr (triple,"powerpc64" ) ||strstr (triple,"ppc64" )) + trunc_len =8 ; +else if (strstr (triple,"powerpc" ) ||strstr (triple,"ppc" )) + trunc_len =4 ; +LLVMDisposeMessage (triple); + } +for (pc =0 , i =0 ; i < jited_funcs; ++i) {fprintf (text_out,"func #%d:\n" , i); -disasm_one_func (text_out, image + pc, func_lens[i]); + +/* +* Disabled JIT have zero func_lens, hence underflow +*/ + disasm_len = func_lens[i] > trunc_len ? + func_lens[i] - trunc_len :0 ; +disasm_one_func (text_out, image + pc, disasm_len); +fprintf (text_out,"\n" ); pc += func_lens[i]; }
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v13 6/8] selftests/bpf: Fix powerpc JIT disassembly failure
Does the changelog accurately describe when this fix takes effect?
Quoted 10 lines
selftests/bpf: Fix powerpc JIT disassembly failure
Ensure that the trampoline stubs JITed at the tail of the epilogue do not expose the dummy trampoline address stored in the last 8 bytes(64-bit) and last 4 bytes(32-bit) to the disassembly flow. Prevent the disassembler from ingesting this memory address, as it may occasionally decode into a seemingly valid but incorrect instruction. Fix this issue by truncating the last 8/4 bytes from JITed buffers before supplying them for disassembly.
Does the changelog accurately describe when this fix takes effect?
Looking at this commit, get_jited_program_text() is only called from run_subtest() in tools/testing/selftests/bpf/test_loader.c when subspec->jited.cnt != 0. The __jited expectations are only recorded when the preceding __arch_* tag matches the running architecture:
} else if ((val = str_has_pfx(s, "test_arch="))) {
...
collect_jit = get_current_arch() == arch;Since get_current_arch() in this tree has no powerpc case, it returns ARCH_UNKNOWN, no __arch_* tag ever matches on powerpc, jited.cnt stays 0, and get_jited_program_text() is never invoked on powerpc.
The path only becomes reachable with the later patches in the same series, 422d7386c6a2b (which adds ARCH_POWERPC64 / __arch_powerpc64) and b787dda9a979d (which adds tailcall verifier selftest). Would it be more accurate to describe this as a prerequisite for that enablement rather than as a fix for a current failure?
Also, the changelog says the address "may occasionally decode into a seemingly valid but incorrect instruction", whereas the comment added by this same hunk says the bytes "can't disassemble to a valid instruction", and the companion patch 0cd8bd7da278d quotes the concrete symptom:
Can't disasm instruction at offset 104: c0 34 1d 00 00 00 00 c0
A hard "Can't disasm" abort (what disasm_insn() reports via PRINT_FAIL) is a different problem from a mis-decode into a plausible-looking instruction. Could the commit message be more precise about which failure mode actually occurs?
---
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/33368376983
Re: [PATCH v13 6/8] selftests/bpf: Fix powerpc JIT disassembly failure
In reply to Saket Kumar BhaskarQuoted 11 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
Ensure that the trampoline stubs JITed at the tail of the epilogue do not expose the dummy trampoline address stored in the last 8 bytes(64-bit) and last 4 bytes(32-bit) to the disassembly flow. Prevent the disassembler from ingesting this memory address, as it may occasionally decode into a seemingly valid but incorrect instruction. Fix this issue by truncating the last 8/4 bytes from JITed buffers before supplying them for disassembly.
Quoted 4 lines
Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>Reviewed-by Hari Bathini <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 52 lines
--- .../selftests/bpf/jit_disasm_helpers.c | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-)tools/testing/selftests/bpf/jit_disasm_helpers.c +24
-1@@ -179,9 +179,11 @@ int get_jited_program_text(int fd, char *text, size_t text_sz)struct bpf_prog_info info = {};__u32 info_len =sizeof (info);__u32 jited_funcs, len, pc; +__u32 trunc_len =0 , disasm_len;__u32 *func_lens =NULL ;FILE *text_out =NULL ;uint8_t *image =NULL ; +char *triple =NULL ;int i, err =0 ;if (!llvm_initialized) {@@ -225,9 +227,30 @@ int get_jited_program_text(int fd, char *text, size_t text_sz)if (!ASSERT_OK (err,"bpf_prog_get_info_by_fd #2" )) goto out; +/* +* last 8 bytes contains dummy_trampoline address in JIT +* output on 64-bit and last 4 bytes on 32-bit powerpc, +* which can't disassemble to a valid instruction. +*/ + triple =LLVMGetDefaultTargetTriple (); +if (triple) { +if (strstr (triple,"powerpc64" ) ||strstr (triple,"ppc64" )) + trunc_len =8 ; +else if (strstr (triple,"powerpc" ) ||strstr (triple,"ppc" )) + trunc_len =4 ; +LLVMDisposeMessage (triple); + } +for (pc =0 , i =0 ; i < jited_funcs; ++i) {fprintf (text_out,"func #%d:\n" , i); -disasm_one_func (text_out, image + pc, func_lens[i]); + +/* +* Disabled JIT have zero func_lens, hence underflow +*/ + disasm_len = func_lens[i] > trunc_len ? + func_lens[i] - trunc_len :0 ; +disasm_one_func (text_out, image + pc, disasm_len); +fprintf (text_out,"\n" ); pc += func_lens[i]; }
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry
PATCH v13 7/8 selftests/bpf: Enable verifier selftest for powerpc64
In reply to Saket Kumar BhaskarFrom: Abhishek Dubey <[email protected]>
Enable "__arch_powerpc64" specifier in verifier selftest for ppc64. Power 32-bit would require separate handling. Changes tested for 64-bit only.
--- tools/testing/selftests/bpf/progs/bpf_misc.h | 1 + tools/testing/selftests/bpf/test_loader.c | 5 +++++ 2 files changed, 6 insertions(+)
tools/testing/selftests/bpf/progs/bpf_misc.h +1 -0
@@ -159,6 +159,7 @@#define __arch_riscv64 __arch("RISCV64")#define __arch_s390x __arch("s390x")#define __arch_loongarch __arch("LOONGARCH") +#define __arch_powerpc64 __arch("POWERPC64")#define __caps_unpriv (caps) __test_tag("test_caps_unpriv=" EXPAND_QUOTE(caps))#define __load_if_JITed () __test_tag("load_mode=jited")#define __load_if_no_JITed () __test_tag("load_mode=no_jited")
tools/testing/selftests/bpf/test_loader.c +5 -0
@@ -378,6 +378,7 @@ enum arch {ARCH_RISCV64 =0x8 ,ARCH_S390X =0x10 ,ARCH_LOONGARCH =0x20 , + ARCH_POWERPC64 = 0x40, };static int get_current_arch (void )
@@ -392,6 +393,8 @@ static int get_current_arch(void)return ARCH_S390X ;#elif defined(__loongarch__)return ARCH_LOONGARCH ; +#elif defined(__powerpc64__) +return ARCH_POWERPC64 ;#endif return ARCH_UNKNOWN ; }
@@ -585,6 +588,8 @@ static int parse_test_spec(struct test_loader *tester, arch =ARCH_S390X ; }else if (strcmp (val,"LOONGARCH" ) ==0 ) { arch =ARCH_LOONGARCH ; + }else if (strcmp (val,"POWERPC64" ) ==0 ) { + arch =ARCH_POWERPC64 ; }else {PRINT_FAIL ("bad arch spec: '%s'\n" , val); err = -EINVAL ;
Signature · 1 line
2.55.0
Re: [PATCH v13 7/8] selftests/bpf: Enable verifier selftest for powerpc64
Quoted 10 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
Enable "
__arch_powerpc64" specifier in verifier selftest for ppc64. Power 32-bit would require separate handling. Changes tested for 64-bit only.Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>Reviewed-by Hari Bathini <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 47 lines
--- tools/testing/selftests/bpf/progs/bpf_misc.h | 1 + tools/testing/selftests/bpf/test_loader.c | 5 +++++ 2 files changed, 6 insertions(+)tools/testing/selftests/bpf/progs/bpf_misc.h +1
-0@@ -159,6 +159,7 @@#define __arch_riscv64 __arch("RISCV64")#define __arch_s390x __arch("s390x")#define __arch_loongarch __arch("LOONGARCH") +#define __arch_powerpc64 __arch("POWERPC64")#define __caps_unpriv (caps) __test_tag("test_caps_unpriv=" EXPAND_QUOTE(caps))#define __load_if_JITed () __test_tag("load_mode=jited")#define __load_if_no_JITed () __test_tag("load_mode=no_jited")tools/testing/selftests/bpf/test_loader.c +5
-0@@ -378,6 +378,7 @@ enum arch {ARCH_RISCV64 =0x8 ,ARCH_S390X =0x10 ,ARCH_LOONGARCH =0x20 , + ARCH_POWERPC64 = 0x40, };static int get_current_arch (void )@@ -392,6 +393,8 @@ static int get_current_arch(void)return ARCH_S390X ;#elif defined(__loongarch__)return ARCH_LOONGARCH ; +#elif defined(__powerpc64__) +return ARCH_POWERPC64 ;#endif return ARCH_UNKNOWN ; }@@ -585,6 +588,8 @@ static int parse_test_spec(struct test_loader *tester, arch =ARCH_S390X ; }else if (strcmp (val,"LOONGARCH" ) ==0 ) { arch =ARCH_LOONGARCH ; + }else if (strcmp (val,"POWERPC64" ) ==0 ) { + arch =ARCH_POWERPC64 ; }else {PRINT_FAIL ("bad arch spec: '%s'\n" , val); err = -EINVAL ;
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry
PATCH v13 8/8 selftests/bpf: Add tailcall verifier selftest for powerpc64
In reply to Saket Kumar BhaskarFrom: Abhishek Dubey <[email protected]>
Verifier testcase result for tailcalls:
# ./test_progs -t verifier_tailcall
#618/1 verifier_tailcall/invalid map type for tail call:OK
#618/2 verifier_tailcall/invalid map type for tail call @unpriv:OK
#618 verifier_tailcall:OK
#619/1 verifier_tailcall_jit/main:OK
#619 verifier_tailcall_jit:OK
Summary: 2/3 PASSED, 0 SKIPPED, 0 FAILED
--- .../bpf/progs/verifier_tailcall_jit.c | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+)
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +78 -0
@@ -91,6 +91,84 @@ __jited(" popq %rax") __jited(" jmp {{.*}}") /* jump to tail call tgt */ __jited("L0: leave") __jited(" {{(retq|jmp 0x)}}") /* return or jump to rethunk */ +__arch_powerpc64 +/* program entry for main(), regular function prologue */ +__jited(" nop") +__jited("...") /* ld 2, 16(13) absent with CONFIG_PPC_KERNEL_PCREL */ +__jited(" li 9, 0") +__jited(" std 9, -8(1)") +__jited(" mflr 0") +__jited(" std 0, 16(1)") +__jited (" stdu 1, {{.*}}(1)" ) +/* +* load address and call sub() via count register +* +* Address materialization differs between PCREL and non-PCREL kernels. +* Skip the address generation sequence and verify only that the call +* target is loaded into CTR before branching. + */ +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited(" mtctr {{.*}}") +__jited("...") +__jited(" bctrl") +__jited(" mr 8, 3") +__jited(" li 8, 0") +__jited(" addi 1, 1, {{.*}}") +__jited(" ld 0, 16(1)") +__jited(" mtlr 0") +__jited(" mr 3, 8") +__jited(" blr") +__jited("...") +__jited ("func #1" ) +/* subprogram entry for sub() */ +__jited(" nop") +__jited ("..." )/* ld 2, 16(13) absent with CONFIG_PPC_KERNEL_PCREL */ +/* tail call prologue for subprogram */ +__jited(" ld 10, 0(1)") +__jited(" ld 9, -8(10)") +__jited(" cmpldi 9, 33") +__jited(" bt {{.*}}, {{.*}}") +__jited(" addi 9, 10, -8") +__jited(" std 9, -8(1)") +__jited(" lis {{.*}}, {{.*}}") +__jited(" sldi {{.*}}, {{.*}}, 32") +__jited(" oris {{.*}}, {{.*}}, {{.*}}") +__jited(" ori {{.*}}, {{.*}}, {{.*}}") +__jited(" li {{.*}}, 0") +__jited(" lwz 9, {{.*}}({{.*}})") +__jited(" slwi {{.*}}, {{.*}}, 0") +__jited(" cmplw {{.*}}, 9") +__jited (" bf 0, {{.*}}" ) +/* bpf_tail_call implementation */ +__jited(" ld 9, -8(1)") +__jited(" cmpldi 9, 33") +__jited(" bf {{.*}}, {{.*}}") +__jited(" ld 9, 0(9)") +__jited(" cmpldi 9, 33") +__jited(" bt {{.*}}, {{.*}}") +__jited(" addi 9, 9, 1") +__jited(" mulli 10, {{.*}}, 8") +__jited(" add 10, 10, {{.*}}") +__jited(" ld 10, {{.*}}(10)") +__jited(" cmpldi 10, 0") +__jited(" bt {{.*}}, {{.*}}") +__jited(" ld 10, {{.*}}(10)") +__jited(" addi 10, 10, {{.*}}") /* offset depends on CONFIG_PPC_KERNEL_PCREL */ +__jited(" mtctr 10") +__jited(" ld 10, -8(1)") +__jited(" cmpldi 10, 33") +__jited(" bt {{.*}}, {{.*}}") +__jited(" addi 10, 1, -8") +__jited(" std 9, 0(10)") +__jited(" bctr") +__jited(" mr 3, 8") +__jited(" blr") + SEC("tc")__naked int main (void ) {
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v13 8/8] selftests/bpf: Add tailcall verifier selftest for powerpc64
Will this test work on CONFIG_PPC64_ELF_ABI_V1 builds? On big-endian ppc64 kernels, arch/powerpc/net/bpf_jit_comp.c prepends a 24-byte function descriptor to the JIT image:
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c __jited(" popq %rax") · line 96
Quoted 11 lines
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +3
-0@@ -91,6 +91,84 @@ __jited(" popq %rax") __jited(" jmp {{.*}}") /* jump to tail call tgt */ __jited("L0: leave") __jited(" {{(retq|jmp 0x)}}") /* return or jump to rethunk */ +__arch_powerpc64 +/* program entry for main(), regular function prologue */ +__jited(" nop")
Will this test work on CONFIG_PPC64_ELF_ABI_V1 builds? On big-endian ppc64 kernels, arch/powerpc/net/bpf_jit_comp.c prepends a 24-byte function descriptor to the JIT image:
#ifdef CONFIG_PPC64_ELF_ABI_V1
/* Function descriptor nastiness: Address + TOC */
((u64 *)image)[0] = (u64)fcode_base;
((u64 *)image)[1] = local_paca->kernel_toc;
#endif
fp->bpf_func = (void *)fimage;
fp->jited_len = cgctx.idx * 4 + FUNCTION_DESCR_SIZE;and bpf_prog_get_info_by_fd() copies jited_len bytes starting at prog->bpf_func. The disassembler then receives those 24 descriptor bytes as if they were instructions. The low half of a typical kernel_toc address such as 0xc000000001b70000 decodes from primary opcode 0, which is illegal, causing disasm_insn() to call PRINT_FAIL("Can't disasm instruction...") and fail the test.
The companion commit 9e3a62e5e00a6 truncates the trailing 8 bytes for this reason, but nothing strips the leading descriptor. Should the test be restricted to ELFv2, or should jit_disasm_helpers.c skip FUNCTION_DESCR_SIZE at the head of each function on ppc64 ELFv1?
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c __jited(" popq %rax") · line 97
Quoted 1 line
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +1
-0+__jited ("..." )/* ld 2, 16(13) absent with CONFIG_PPC_KERNEL_PCREL */
Is this comment accurate? The TOC load is guarded by both conditions:
#ifndef CONFIG_PPC_KERNEL_PCREL
if (IS_ENABLED(CONFIG_PPC64_ELF_ABI_V2))
EMIT(PPC_RAW_LD(_R2, _R13, offsetof(struct paca_struct, kernel_toc)));#endif
so it is also absent on CONFIG_PPC64_ELF_ABI_V1 builds.
Also, is the displacement always 16? The offsetof(struct paca_struct, kernel_toc) is 16 only when CONFIG_PPC_PSERIES=y, because the leading 'struct lppaca *lppaca_ptr' member is inside '#ifdef CONFIG_PPC_PSERIES'. Without PSERIES the offset is 8, giving 'ld 2, 8(13)'.
Quoted 8 lines
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +8
-0+__jited(" li 9, 0") +__jited(" std 9, -8(1)") +__jited(" mflr 0") +__jited(" std 0, 16(1)") +__jited (" stdu 1, {{.*}}(1)" ) +/* +* load address and call sub() via count register +*
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c __jited(" popq %rax") · line 115
Quoted 10 lines
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +10
-0+* Address materialization differs between PCREL and non-PCREL kernels. +* Skip the address generation sequence and verify only that the call +* target is loaded into CTR before branching. +*/ +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited ("..." )
Does the framework need six consecutive '...' entries? In tools/testing/selftests/bpf/test_loader.c, the push_disasm_msg() function handles '...' by setting on_next_line = false and returning immediately without pushing a pattern:
if (strcmp(regex_str, "...") == 0) {
*on_next_line = false;
return 0;
}So '...' pushes no pattern at all; it only clears the on_next_line flag for the following real pattern, which then matches anywhere later in the log. Consecutive '...' entries are idempotent. These six lines behave identically to a single '...', and five of them are dead. The comment describes skipping 6 instructions, but the framework doesn't implement per-line skips this way.
Quoted 1 line
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +1
-0+__jited (" mtctr {{.*}}" )
[ ... ]
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c __jited(" popq %rax") · line 130
Quoted 5 lines
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +5
-0+__jited("...") +__jited ("func #1" ) +/* subprogram entry for sub() */ +__jited(" nop") +__jited ("..." )/* ld 2, 16(13) absent with CONFIG_PPC_KERNEL_PCREL */
This comment is repeated verbatim from the main() prologue and has the same
issues: the TOC load is also absent on ELFv1, and the displacement varies with CONFIG_PPC_PSERIES.
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c __jited(" popq %rax") · line 141
Quoted 11 lines
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +11
-0+/* tail call prologue for subprogram */ +__jited(" ld 10, 0(1)") +__jited(" ld 9, -8(10)") +__jited(" cmpldi 9, 33") +__jited(" bt {{.*}}, {{.*}}") +__jited(" addi 9, 10, -8") +__jited(" std 9, -8(1)") +__jited(" lis {{.*}}, {{.*}}") +__jited(" sldi {{.*}}, {{.*}}, 32") +__jited(" oris {{.*}}, {{.*}}, {{.*}}") +__jited (" ori {{.*}}, {{.*}}, {{.*}}" )
Will this lis/sldi/oris/ori sequence always appear in exactly this form? PPC_LI64() in arch/powerpc/net/bpf_jit.h emits between 2 and 5 instructions, conditionally, based on the runtime value:
EMIT(PPC_RAW_LIS(d, (uintptr_t)(i) >> 48));
if ((uintptr_t)(i) & 0x0000ffff00000000ULL)
EMIT(PPC_RAW_ORI(d, d, ((uintptr_t)(i) >> 32) & 0xffff));
EMIT(PPC_RAW_SLDI(d, d, 32));
if ((uintptr_t)(i) & 0x00000000ffff0000ULL)
EMIT(PPC_RAW_ORIS(d, d, ((uintptr_t)(i) >> 16) & 0xffff));
if ((uintptr_t)(i) & 0x000000000000ffffULL)
EMIT(PPC_RAW_ORI(d, d, (uintptr_t)(i) & 0xffff));Each of these four patterns follows a non-'...' pattern, so test_loader.c sets on_next_line=true and requires strict line adjacency. The verifier rewrites BPF_LD_IMM64 to the kernel address of the kvzalloc()'d 'struct bpf_array', which is a linear-map address 0xc000000000000000 + physaddr. When that object lands above the 4 GiB boundary (routine with >4 GiB RAM), PPC_LI64() emits an extra 'ori' between 'lis' and 'sldi', and the 'sldi' pattern then matches one line late, causing validate_msgs() to report WRONG LINE.
Symmetrically, a 64 KiB-aligned map address drops the trailing 'ori', and a map whose bits 16..31 are zero drops the 'oris', both producing EXPECTED/no-match failures. The test already recognizes this problem for the call-target address (the comment about PCREL vs non-PCREL address materialization plus '...' wildcards). Should the map-address materialization use a single '__jited("...")' instead?
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c __jited(" popq %rax") · line 143
Quoted 2 lines
tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +2
-0+__jited(" li {{.*}}, 0") +__jited (" lwz 9, {{.*}}({{.*}})" )
[ ... ]
---
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/33368376983
Re: [PATCH v13 8/8] selftests/bpf: Add tailcall verifier selftest for powerpc64
In reply to Saket Kumar BhaskarQuoted 14 lines · Saket Kumar Bhaskar
From: Abhishek Dubey <[email protected]>
Verifier testcase result for tailcalls:
# ./test_progs -t verifier_tailcall#618/1 verifier_tailcall/invalid map type for tail call:OK
#618/2 verifier_tailcall/invalid map type for tail call @unpriv:OK
#618 verifier_tailcall:OK
#619/1 verifier_tailcall_jit/main:OK
Quoted 7 lines
#619 verifier_tailcall_jit:OK
Summary: 2/3 PASSED, 0 SKIPPED, 0 FAILED
Signed-off-by Abhishek Dubey <[email protected]>Signed-off-by Saket Kumar Bhaskar <[email protected]>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
Quoted 93 lines
--- .../bpf/progs/verifier_tailcall_jit.c | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+)tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c +78
-0@@ -91,6 +91,84 @@ __jited(" popq %rax") __jited(" jmp {{.*}}") /* jump to tail call tgt */ __jited("L0: leave") __jited(" {{(retq|jmp 0x)}}") /* return or jump to rethunk */ +__arch_powerpc64 +/* program entry for main(), regular function prologue */ +__jited(" nop") +__jited("...") /* ld 2, 16(13) absent with CONFIG_PPC_KERNEL_PCREL */ +__jited(" li 9, 0") +__jited(" std 9, -8(1)") +__jited(" mflr 0") +__jited(" std 0, 16(1)") +__jited (" stdu 1, {{.*}}(1)" ) +/* +* load address and call sub() via count register +* +* Address materialization differs between PCREL and non-PCREL kernels. +* Skip the address generation sequence and verify only that the call +* target is loaded into CTR before branching. + */ +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited("...") +__jited(" mtctr {{.*}}") +__jited("...") +__jited(" bctrl") +__jited(" mr 8, 3") +__jited(" li 8, 0") +__jited(" addi 1, 1, {{.*}}") +__jited(" ld 0, 16(1)") +__jited(" mtlr 0") +__jited(" mr 3, 8") +__jited(" blr") +__jited("...") +__jited ("func #1" ) +/* subprogram entry for sub() */ +__jited(" nop") +__jited ("..." )/* ld 2, 16(13) absent with CONFIG_PPC_KERNEL_PCREL */ +/* tail call prologue for subprogram */ +__jited(" ld 10, 0(1)") +__jited(" ld 9, -8(10)") +__jited(" cmpldi 9, 33") +__jited(" bt {{.*}}, {{.*}}") +__jited(" addi 9, 10, -8") +__jited(" std 9, -8(1)") +__jited(" lis {{.*}}, {{.*}}") +__jited(" sldi {{.*}}, {{.*}}, 32") +__jited(" oris {{.*}}, {{.*}}, {{.*}}") +__jited(" ori {{.*}}, {{.*}}, {{.*}}") +__jited(" li {{.*}}, 0") +__jited(" lwz 9, {{.*}}({{.*}})") +__jited(" slwi {{.*}}, {{.*}}, 0") +__jited(" cmplw {{.*}}, 9") +__jited (" bf 0, {{.*}}" ) +/* bpf_tail_call implementation */ +__jited(" ld 9, -8(1)") +__jited(" cmpldi 9, 33") +__jited(" bf {{.*}}, {{.*}}") +__jited(" ld 9, 0(9)") +__jited(" cmpldi 9, 33") +__jited(" bt {{.*}}, {{.*}}") +__jited(" addi 9, 9, 1") +__jited(" mulli 10, {{.*}}, 8") +__jited(" add 10, 10, {{.*}}") +__jited(" ld 10, {{.*}}(10)") +__jited(" cmpldi 10, 0") +__jited(" bt {{.*}}, {{.*}}") +__jited(" ld 10, {{.*}}(10)") +__jited(" addi 10, 10, {{.*}}") /* offset depends on CONFIG_PPC_KERNEL_PCREL */ +__jited(" mtctr 10") +__jited(" ld 10, -8(1)") +__jited(" cmpldi 10, 33") +__jited(" bt {{.*}}, {{.*}}") +__jited(" addi 10, 1, -8") +__jited(" std 9, 0(10)") +__jited(" bctr") +__jited(" mr 3, 8") +__jited(" blr") + SEC("tc")__naked int main (void ) {
Signature · 2 lines
Thanks and Regards R.Nageswara Sastry