netvsc_vf_setxdp() has two kinds of callers. netvsc_register_vf() runs with the VF's instance lock already held, either by the NETDEV_REGISTER notifier or by netvsc_probe() taking it, which is why the propagation moved to the caller-locked netif_xdp_propagate(). netvsc_bpf() runs with just RTNL, so the VF's ndo_bpf() gets called unlocked. For a VF with an ops lock that trips the lockdep assertion in dev_get_min_mp_channel_count(), which netif_xdp_propagate() calls, and races with binding a memory provider, which takes only the instance lock.
Take the VF's lock in netvsc_bpf(). Reported by Sashiko during core rework. Unverified and untested. Cc: [email protected] # LLM report + LLM fix, untested Fixes: 3ec523304976 ("hv_netvsc: fix potential deadlock in netvsc_vf_setxdp()") Signed-off-by: Jakub Kicinski <[email protected]> --- drivers/net/hyperv/netvsc_bpf.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/net/hyperv/netvsc_bpf.c b/drivers/net/hyperv/netvsc_bpf.c index 731bb7721fe2..951c19ce15eb 100644 --- a/drivers/net/hyperv/netvsc_bpf.c +++ b/drivers/net/hyperv/netvsc_bpf.c @@ -14,6 +14,7 @@ #include <linux/bpf.h> #include <linux/bpf_trace.h> #include <linux/kernel.h> +#include <net/netdev_lock.h> #include <net/xdp.h> #include <linux/mutex.h> @@ -162,6 +163,7 @@ int netvsc_xdp_set(struct net_device *dev, struct bpf_prog *prog, return 0; } +/* Caller holds the VF's lock, see netdev_lock_ops() */ int netvsc_vf_setxdp(struct net_device *vf_netdev, struct bpf_prog *prog) { struct netdev_bpf xdp; @@ -172,6 +174,8 @@ int netvsc_vf_setxdp(struct net_device *vf_netdev, struct bpf_prog *prog) if (!vf_netdev) return 0; + netdev_assert_locked_ops_compat(vf_netdev); + if (!vf_netdev->netdev_ops->ndo_bpf) return 0; @@ -210,7 +214,15 @@ int netvsc_bpf(struct net_device *dev, struct netdev_bpf *bpf) if (ret) return ret; - ret = netvsc_vf_setxdp(vf_netdev, bpf->prog); + /* Unlike netvsc_register_vf() we don't get the VF's lock + * handed to us here. + */ + ret = 0; + if (vf_netdev) { + netdev_lock_ops(vf_netdev); + ret = netvsc_vf_setxdp(vf_netdev, bpf->prog); + netdev_unlock_ops(vf_netdev); + } if (ret) { netdev_err(dev, "vf_setxdp failed:%d\n", ret); -- 2.55.0
