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 a1aa9ef47c ("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: ab3f0063c4 ("bpf: offload: add infrastructure for loading programs for a specific netdev")
Fixes: 2b3486bc2d ("bpf: Introduce device-bound XDP programs")
Fixes: a1aa9ef47c ("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
This commit is contained in:
Josef Bacik 2026-10-17 16:37:22 +00:00 committed by Alexei Starovoitov
parent 45e912fb1d
commit 8e83d53596
No known key found for this signature in database
3 changed files with 23 additions and 2 deletions

View File

@ -3792,6 +3792,7 @@ int bpf_dev_bound_kfunc_check(struct bpf_verifier_log *log,
void *bpf_dev_bound_resolve_kfunc(struct bpf_prog *prog, u32 func_id);
int bpf_prog_dev_bound_init(struct bpf_prog *prog, union bpf_attr *attr);
int bpf_prog_dev_bound_inherit(struct bpf_prog *new_prog, struct bpf_prog *old_prog);
void bpf_prog_dev_bound_moved(struct bpf_prog *prog);
void bpf_dev_bound_netdev_unregister(struct net_device *dev);
static inline bool bpf_prog_is_dev_bound(const struct bpf_prog_aux *aux)
@ -3853,6 +3854,10 @@ static inline int bpf_prog_dev_bound_inherit(struct bpf_prog *new_prog,
return -EOPNOTSUPP;
}
static inline void bpf_prog_dev_bound_moved(struct bpf_prog *prog)
{
}
static inline void bpf_dev_bound_netdev_unregister(struct net_device *dev)
{
}

View File

@ -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);
}

View File

@ -96,6 +96,22 @@ static int __bpf_offload_dev_netdev_register(struct bpf_offload_dev *offdev,
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;