Skip to contentSkip to conversation

powerpc/bpf: address missing verifier selftest coverage

PATCH Superseded Saket Kumar Bhaskar 5 participants 27 messages Aug 31 – Sep 8

Subsystems (6)
Stable branch PowerPC Kselftest BPF +2 more subsystems

v13 of 14 Superseded Latest is v14 →

lore
Share & tools
lore
#0 lore raw

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 for
CONFIG_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
#1 lore raw

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
index f32de8704d4d..35015d7ecb76 100644
@@ -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
index 7b07b43575f1..a875521ff904 100644
@@ -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
index dab106cae22b..59302cabc466 100644
@@ -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 o
 	PPC_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
Tested-by on v13 1/8
#2 lore raw

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_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.

Quoted 7 lines

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
index f32de8704d4d..35015d7ecb76 100644
@@ -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
index 7b07b43575f1..a875521ff904 100644
@@ -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
index dab106cae22b..59302cabc466 100644
@@ -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 o
  	PPC_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
#3 lore raw

PATCH v13 2/8 powerpc64/bpf: fix percpu private stack leak on JIT failure

In reply to Saket Kumar Bhaskar

From: 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
index a875521ff904..8f7501954d9f 100644
@@ -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
#4 lore raw

Re: [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
index a875521ff904d..8f7501954d9f0 100644
@@ -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
Tested-by on v13 2/8
#5 lore raw

Re: [PATCH v13 2/8] powerpc64/bpf: fix percpu private stack leak on JIT failure

In reply to Saket Kumar Bhaskar
Quoted 19 lines · Saket Kumar Bhaskar

From: 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.

Quoted 8 lines

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
index a875521ff904..8f7501954d9f 100644
@@ -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
#6 lore raw

PATCH v13 3/8 powerpc/bpf: fix buffer overflow in JIT for large BPF programs

In reply to Saket Kumar Bhaskar

From: 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
index 35015d7ecb76..6d58df361648 100644
@@ -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
index 8f7501954d9f..11981d2270a9 100644
@@ -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_pr
 			bpf_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 pass
 	u32 *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
#7 lore raw

Re: [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:

Fixes 0ffdbce6f4a8 ("powerpc/bpf: Handle large branch ranges with BPF_EXIT")
Quoted 6 lines
arch/powerpc/net/bpf_jit_comp.c +0 -0
index 8f7501954d9f0..11981d2270a9d 100644
@@ -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 -4
 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))) {
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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 -0
 		PPC_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 pass
 	u32 *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

maintainer
#8 lore raw

Re: [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 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.

Quoted 9 lines

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:

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
index 8f7501954d9f0..11981d2270a9d 100644
@@ -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 -4
  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))) {
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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 -0
  		PPC_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 pass
  	u32 *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

Tested-by on v13 3/8
#9 lore raw

Re: [PATCH v13 3/8] powerpc/bpf: fix buffer overflow in JIT for large BPF programs

In reply to Saket Kumar Bhaskar
Quoted 37 lines · Saket Kumar Bhaskar

From: 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.

Quoted 9 lines

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
index 35015d7ecb76..6d58df361648 100644
@@ -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
index 8f7501954d9f..11981d2270a9 100644
@@ -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_pr
  			bpf_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 pass
  	u32 *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
#10 lore raw

PATCH v13 4/8 powerpc/bpf: fix alignment of long branch trampoline address

In reply to 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.

Fixes d243b62b7bd3 ("powerpc64/bpf: Add support for bpf trampolines")
---
 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
index 6d58df361648..4da8bde92e1e 100644
@@ -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
index 11981d2270a9..8ca36a933c7a 100644
@@ -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
index bfdc50740da8..f5b9441cf46a 100644
@@ -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
index 59302cabc466..e80312171aa7 100644
@@ -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
#11 lore raw

Re: [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
index 11981d2270a9d..8ca36a933c7ae 100644

[ ... ]

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

maintainer
#12 lore raw

Re: [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
index 11981d2270a9d..8ca36a933c7ae 100644

[ ... ]

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 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.)

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

maintainer Reviewed-by
#13 lore raw

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
index 11981d2270a9d..8ca36a933c7ae 100644

[ ... ]

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)
+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

Quoted 8 lines

+     * 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 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.)

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:

Tested-by on v13 4/8
#14 lore raw

Re: [PATCH v13 4/8] powerpc/bpf: fix alignment of long branch trampoline address

In reply to Saket Kumar Bhaskar
Quoted 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
Fixes d243b62b7bd3 ("powerpc64/bpf: Add support for bpf trampolines")

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
index 6d58df361648..4da8bde92e1e 100644
@@ -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
index 11981d2270a9..8ca36a933c7a 100644
@@ -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
index bfdc50740da8..f5b9441cf46a 100644
@@ -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
index 59302cabc466..e80312171aa7 100644
@@ -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
#15 lore raw

PATCH v13 5/8 powerpc/bpf: Move out dummy_tramp_addr after Long branch stub

In reply to 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_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
index 8ca36a933c7a..c2717f0d9cdd 100644
@@ -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
#16 lore raw

Re: [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
index 8ca36a933c7ae..c2717f0d9cdd4 100644

[ ... ]

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);
		else

While 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
maintainer Reviewed-by on v13 5/8
#17 lore raw

Re: [PATCH v13 5/8] powerpc/bpf: Move out dummy_tramp_addr after Long branch stub

In reply to Saket Kumar Bhaskar
Quoted 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_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.

Quoted 6 lines

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.

This makes it easy to disassemble the bpf programs. Looks good.

Quoted 126 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
index 8ca36a933c7a..c2717f0d9cdd 100644
@@ -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)
Tested-by on v13 5/8
#18 lore raw

Re: [PATCH v13 5/8] powerpc/bpf: Move out dummy_tramp_addr after Long branch stub

In reply to Saket Kumar Bhaskar
Quoted 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_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.

Quoted 5 lines

All BPF trampoline selftests continue to pass with this patch applied.

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
index 8ca36a933c7a..c2717f0d9cdd 100644
@@ -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
#19 lore raw

PATCH v13 6/8 selftests/bpf: Fix powerpc JIT disassembly failure

In reply to 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.

---
 .../selftests/bpf/jit_disasm_helpers.c        | 25 ++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)
tools/testing/selftests/bpf/jit_disasm_helpers.c +24 -1
index 3558fe10e28c..b291d934dd1a 100644
@@ -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
#20 lore raw

Re: [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

Tested-by on v13 6/8
#21 lore raw

Re: [PATCH v13 6/8] selftests/bpf: Fix powerpc JIT disassembly failure

In reply to Saket Kumar Bhaskar
Quoted 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

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
index 3558fe10e28c..b291d934dd1a 100644
@@ -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
#22 lore raw

PATCH v13 7/8 selftests/bpf: Enable verifier selftest for powerpc64

In reply to 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.

---
 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
index b0c441384f20..365008d1ed40 100644
@@ -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
index 3ce32d134e2c..01b1420097d2 100644
@@ -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
Tested-by on v13 7/8
#23 lore raw

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.

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
index b0c441384f20..365008d1ed40 100644
@@ -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
index 3ce32d134e2c..01b1420097d2 100644
@@ -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
#24 lore raw

PATCH v13 8/8 selftests/bpf: Add tailcall verifier selftest for powerpc64

In reply to 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

#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
index 48fa34d2959f..9880ce4f1931 100644
@@ -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
#25 lore raw

Re: [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
index 48fa34d2959f4..9880ce4f1931a 100644
@@ -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

Tested-by on v13 8/8
#26 lore raw

Re: [PATCH v13 8/8] selftests/bpf: Add tailcall verifier selftest for powerpc64

In reply to Saket Kumar Bhaskar
Quoted 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

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
index 48fa34d2959f..9880ce4f1931 100644
@@ -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