[PATCH v2] bpf, arm64: Fix racy plt target update in bpf_arch_text_poke()

Xu Kuohai xukuohai at huaweicloud.com
Wed Oct 7 21:07:43 PDT 2026


On 10/2/2026 8:12 PM, Matthew Wood wrote:
> When a long-jump trampoline is attached to or detached from a bpf prog,
> bpf_arch_text_poke() updates the plt target at the end of the prog by
> temporarily making the page writable:
> 
> 	set_memory_rw(page);
> 	WRITE_ONCE(plt->target, plt_target);
> 	set_memory_ro(page);
> 
> Since commit 1dad391daef1 ("bpf, arm64: use bpf_prog_pack for memory
> management"), bpf progs are allocated from the bpf prog pack, so
> unrelated progs and their plts commonly share a page. The sequence
> above is not serialized against other pokers, it runs before text_mutex
> is taken and callers only hold the lock of the trampoline being
> updated. Two CPUs attaching to or detaching from different target progs
> in the same page can therefore race:
> 
> 	CPU A                           CPU B
> 	set_memory_rw(page)
> 	                                set_memory_rw(page)
> 	                                WRITE_ONCE(plt_B->target, ...)
> 	                                set_memory_ro(page)
> 	WRITE_ONCE(plt_A->target, ...)  <- permission fault
> 
> This was hit on an arm64 server with 64K pages while attaching fentry
> programs to bpf progs:
> 
>    Unable to handle kernel write to read-only memory at virtual address ffff80008f46d5f8
>    ESR = 0x000000009600004f
>    FSC = 0x0f: level 3 permission fault
>    pte=00c00200ea5a0783
>    Internal error: Oops: 000000009600004f [#1]  SMP
>    pc : bpf_arch_text_poke+0x214/0x238
>    lr : bpf_arch_text_poke+0x200/0x238
>    Call trace:
>     bpf_arch_text_poke+0x214/0x238 (P)
>     __bpf_trampoline_link_prog+0x1c8/0x470
>     bpf_trampoline_link_prog+0x64/0x90
>     bpf_tracing_prog_attach+0x318/0x4a8
>     bpf_raw_tp_link_attach+0x104/0x258
>     bpf_raw_tracepoint_open+0x6c/0x90
>     __sys_bpf+0x134c/0x3e10
> 
> Rather than serializing the permission changes, stop changing page
> permissions altogether and write the plt target with
> aarch64_insn_write_literal_u64(). It writes through the text patching
> fixmap under patch_lock, the same way the rest of the prog pack is
> written, and performs a single-copy atomic 64-bit store. The plt target
> is naturally aligned (see build_plt()), so CPUs concurrently executing
> the plt still observe either the old or the new target. This is the
> same helper ftrace and static calls use to update 64-bit literals that
> are loaded concurrently.
> 
> Fixes: 1dad391daef1 ("bpf, arm64: use bpf_prog_pack for memory management")
> Signed-off-by: Matthew Wood <thepacketgeek at gmail.com>
> ---
> Changes in v2:
> - Replace page permissions change with call to aarch64_insn_write_literal_u64
> - Link to v1: https://lore.kernel.org/linux-arm-kernel/CADvopvZPO1gDMDhDOPcArjRJ0cPpSUZ5suMg-1kwRi+_-Xeitw@mail.gmail.com/
> ---
>   arch/arm64/net/bpf_jit_comp.c | 12 +++++++-----
>   1 file changed, 7 insertions(+), 5 deletions(-)
> 
> diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
> index 475e70653454..e353403cc1c3 100644
> --- a/arch/arm64/net/bpf_jit_comp.c
> +++ b/arch/arm64/net/bpf_jit_comp.c
> @@ -3339,13 +3339,15 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t,
>   		plt_target = (u64)&dummy_tramp;
> 
>   	if (plt_target) {
> -		/* non-zero plt_target indicates we're patching a bpf prog,
> -		 * which is read only.
> +		/*
> +		 * non-zero plt_target indicates we're patching a bpf prog,
> +		 * which is read only. The prog shares its page in the bpf
> +		 * prog pack with other progs, so write the aligned target
> +		 * through the text patching fixmap without flipping the
> +		 * page permissions to avoid racing with concurrent pokers.
>   		 */
> -		if (set_memory_rw(PAGE_MASK & ((uintptr_t)&plt->target), 1))
> +		if (aarch64_insn_write_literal_u64(&plt->target, plt_target))
>   			return -EFAULT;
> -		WRITE_ONCE(plt->target, plt_target);
> -		set_memory_ro(PAGE_MASK & ((uintptr_t)&plt->target), 1);
>   		/* since plt target points to either the new trampoline
>   		 * or dummy_tramp, even if another CPU reads the old plt
>   		 * target value before fetching the bl instruction to plt,


I think it's better to combine the two nested if statements into one, and merge
the two comments into a single comment above the new if statement.

Otherwise lgtm.

> --
> 2.53.0-Meta




More information about the linux-arm-kernel mailing list