[PATCH bpf v3 1/2] bpf, tcx, netkit: reject offloaded programs

Jiayuan Chen
2026-04-25T11:00:13Z

An offloaded prog has its bpf_func replaced by bpf_prog_warn_on_exec()
during bpf_prog_offload_compile(), since it is supposed to run on the
NIC. Both current mprog users, tcx and netkit, dispatch programs via
bpf_prog_run() on the host. Attaching an offloaded prog through any
of their entry points (BPF_PROG_ATTACH, BPF_LINK_CREATE, BPF_LINK_UPDATE
on tcx_*/netkit_*) ends up tripping the WARN on the first packet.

Ideally this validation would live in tcx and netkit, since "must not
be offloaded" is a property of those subsystems' software dispatch,
not of the generic multi-prog attachment layer. However, those two
together have six attach call sites and putting the check in each of
them duplicates the same logic. mprog happens to be the only chokepoint
shared by all of them, so add the check there instead and scope it to
BPF_PROG_TYPE_SCHED_CLS via a small helper, so a future mprog user that
legitimately accepts offloaded programs is not affected.

Use bpf_prog_is_offloaded() rather than bpf_prog_is_dev_bound() +
bpf_offload_dev_match() (as XDP does): bpf_prog_dev_bound_init()
already rejects BPF_F_XDP_DEV_BOUND_ONLY for BPF_PROG_TYPE_SCHED_CLS,
so a dev-bound SCHED_CLS program is always offloaded. The simpler
check is sufficient and also rejects attaching a program offloaded to
device A onto device B.

Fixes: 053c8e1f235dc ("bpf: Add generic attach/detach/query API for multi-progs")
Reported-by: Yinhao Hu <dddddd@hust.edu.cn>
Reported-by: Kaiyan Mei <M202472210@hust.edu.cn>
Reported-by: Dongliang Mu <dzm91@hust.edu.cn>
Closes: https://lore.kernel.org/bpf/64d8e2b5-a214-4f3c-b9e8-bcedbcb2c602@hust.edu.cn/
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
kernel/bpf/mprog.c | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
index 1394168062e85..0b50464ec902d 100644
--- a/kernel/bpf/mprog.c
+++ b/kernel/bpf/mprog.c
@@ -222,6 +222,14 @@ static int bpf_mprog_pos_after(struct bpf_mprog_entry *entry,
return tuple->prog ? -ENOENT : bpf_mprog_total(entry);
}

+static int bpf_mprog_check_prog(const struct bpf_prog *prog)
+{
+ if (prog->type == BPF_PROG_TYPE_SCHED_CLS &&
+ bpf_prog_is_offloaded(prog->aux))
+ return -EINVAL;
+ return 0;
+}
+
int bpf_mprog_attach(struct bpf_mprog_entry *entry,
struct bpf_mprog_entry **entry_new,
struct bpf_prog *prog_new, struct bpf_link *link,
@@ -237,6 +245,9 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
};
int ret, idx = -ERANGE, tidx;

+ ret = bpf_mprog_check_prog(prog_new);
+ if (ret)
+ return ret;
if (revision && revision != bpf_mprog_revision(entry))
return -ESTALE;
if (bpf_mprog_exists(entry, prog_new))
--
2.43.0