summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorJosef Bacik <josef@toxicpanda.com>2026-10-17 16:37:22 +0000
committerAlexei Starovoitov <ast@kernel.org>2026-10-17 22:01:13 +0000
commit8e83d53596445fcf5b0385e5fbe8bcd971e06d06 (patch)
tree2d5e3ab693d6e5fe25c83ef400d2edbf92f32455 /kernel
parent45e912fb1db00404267fafd9fd034c68b1f94622 (diff)
downloadlinux-8e83d53596445fcf5b0385e5fbe8bcd971e06d06.tar.gz
linux-8e83d53596445fcf5b0385e5fbe8bcd971e06d06.zip
bpf: Fix stale offload->prog pointer after realloc
bpf_prog_realloc() moves a program that the rewrites of the verifier make outgrow its pages. It updates aux->prog, but not the offload->prog of a device-bound program. When the device is unregistered, __bpf_offload_dev_netdev_unregister() hands the stale pointer to __bpf_prog_offload_destroy(), which faults on the freed program: BUG: unable to handle page fault for address: ffffc90000071050 RIP: 0010:__bpf_prog_offload_destroy+0x3f/0x310 It takes the right to load a device-bound XDP program, CAP_BPF and CAP_NET_ADMIN or a BPF token, and a device that can be removed. Commit a1aa9ef47c29 ("bpf: Fix stale offload->prog pointer after constant blinding") repaired the other place that replaces a program. Do the same here. Nothing keeps the device while a program is loaded, so it can also be unregistered at that very moment. That frees the offload, and it may have read offload->prog already when the old copy of the program is freed. In both places update the pointer under bpf_devs_lock, before the old copy goes. Fixes: ab3f0063c48c ("bpf: offload: add infrastructure for loading programs for a specific netdev") Fixes: 2b3486bc2d23 ("bpf: Introduce device-bound XDP programs") Fixes: a1aa9ef47c29 ("bpf: Fix stale offload->prog pointer after constant blinding") Signed-off-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20261017163745.3341787-21-alexei.starovoitov@gmail.com
Diffstat (limited to 'kernel')
-rw-r--r--kernel/bpf/core.c4
-rw-r--r--kernel/bpf/offload.c16
2 files changed, 18 insertions, 2 deletions
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 642f0ab8eb2d..69285abcf594 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -269,6 +269,7 @@ struct bpf_prog *bpf_prog_realloc(struct bpf_prog *fp_old, unsigned int size,
memcpy(fp, fp_old, fp_old->pages * PAGE_SIZE);
fp->pages = pages;
fp->aux->prog = fp;
+ bpf_prog_dev_bound_moved(fp);
/* We keep fp->aux from fp_old around in the new
* reallocated structure.
@@ -1558,8 +1559,7 @@ void bpf_jit_prog_release_other(struct bpf_prog *fp, struct bpf_prog *fp_other)
* know whether fp here is the clone or the original.
*/
fp->aux->prog = fp;
- if (fp->aux->offload)
- fp->aux->offload->prog = fp;
+ bpf_prog_dev_bound_moved(fp);
bpf_prog_clone_free(fp_other);
}
diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c
index d855399812ee..5a10f40a7831 100644
--- a/kernel/bpf/offload.c
+++ b/kernel/bpf/offload.c
@@ -96,6 +96,22 @@ err_free:
return err;
}
+/*
+ * @prog takes the place of a copy that the caller is about to free. Nothing
+ * keeps the netdev while a program is loaded: once the lock is dropped, no
+ * unregistration is left that has got the old copy from offload->prog.
+ */
+void bpf_prog_dev_bound_moved(struct bpf_prog *prog)
+{
+ if (!bpf_prog_is_dev_bound(prog->aux))
+ return;
+
+ down_read(&bpf_devs_lock);
+ if (prog->aux->offload)
+ prog->aux->offload->prog = prog;
+ up_read(&bpf_devs_lock);
+}
+
static void __bpf_prog_offload_destroy(struct bpf_prog *prog)
{
struct bpf_prog_offload *offload = prog->aux->offload;