[PATCH net] vxlan: vnifilter: roll back VNI insertion when the group update fails

Ali Firas posted 1 patch 2 hours ago
drivers/net/vxlan/vxlan_vnifilter.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
[PATCH net] vxlan: vnifilter: roll back VNI insertion when the group update fails
Posted by Ali Firas 2 hours ago
vxlan_vni_add() inserts the new VNI node into the hash table, adds it to
the device list and registers it with the socket before calling
vxlan_vni_update_group(). If that call fails, none of it is undone and
the error is simply returned, so the VNI stays visible in the hash table
and in "bridge vni show" even though the request failed.

Since commit aa6ca1c5c338 ("vxlan: vnifilter: send notification on VNI
add"), vxlan_vnifilter_notify() is also called unconditionally, so
userspace receives an RTM_NEWTUNNEL notification for a VNI whose
creation returned an error.

This needs no special configuration. All VNIs on a device share the same
socket, so once sysctl_igmp_max_memberships multicast groups have been
joined, the next distinct group fails with -ENOBUFS from
ip_mc_join_group(), after the node has already been published:

  ip link add vx0 type vxlan external vnifilter dstport 4789
  ip link set vx0 up
  for i in $(seq 1 21); do
      bridge vni add vni $i group 239.1.1.$i dev vx0
  done

With the default limit of 20, VNI 21 fails with "No buffer space
available" and is nevertheless listed by "bridge vni show".
sysctl_igmp_max_memberships is tunable and per-netns; the first failing
VNI is the limit plus one. The device can be created and configured by
an unprivileged user holding CAP_NET_ADMIN in a network namespace's user
namespace.

Undo the insertion on the error path, mirroring the teardown order of
vxlan_vni_del(), and only notify on success. vxlan_vni_delete_group() is
safe to call regardless of how far vxlan_vni_update_group() got: if it
failed before installing the FDB entry, both vninode->remote_ip and the
default remote_ip are zero and it does nothing. The node has already
been published in the hash table, so it is freed with call_rcu() as
vxlan_vni_del() does, not with vxlan_vni_free().

Tested in a QEMU guest under KASAN and PROVE_LOCKING. Before the change
VNI 21 is listed after failing and a notification is emitted for it;
after the change the table is empty while the add fails with the same
errno at the same VNI, and ftrace confirms vxlan_vni_update_group() is
still reached for all 22 adds, so the failure does not move earlier. A
VNI that joins successfully is still installed and still notified.

Ido Schimmel pointed out this missing rollback while reviewing an
unrelated fix to vxlan_igmp_join() that was not followed up.

Link: https://lore.kernel.org/netdev/20260323095544.3311285-4-bestswngs@gmail.com/
Fixes: f9c4bb0b245c ("vxlan: vni filtering support on collect metadata device")
Cc: Weiming Shi <bestswngs@gmail.com>
Cc: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Ali Firas <alishmery18@gmail.com>
---
 drivers/net/vxlan/vxlan_vnifilter.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c
index dd94085e0886..ef60a96bcf90 100644
--- a/drivers/net/vxlan/vxlan_vnifilter.c
+++ b/drivers/net/vxlan/vxlan_vnifilter.c
@@ -718,6 +718,8 @@ static void vxlan_vni_free(struct vxlan_vni_node *vninode)
 	kfree(vninode);
 }
 
+static void vxlan_vni_node_rcu_free(struct rcu_head *rcu);
+
 static int vxlan_vni_add(struct vxlan_dev *vxlan,
 			 struct vxlan_vni_group *vg,
 			 u32 vni, union vxlan_addr *group,
@@ -756,9 +758,21 @@ static int vxlan_vni_add(struct vxlan_dev *vxlan,
 
 	err = vxlan_vni_update_group(vxlan, vninode, group, true, &changed,
 				     extack);
+	if (err)
+		goto err_vni_del;
 
 	vxlan_vnifilter_notify(vxlan, vninode, RTM_NEWTUNNEL);
 
+	return 0;
+
+err_vni_del:
+	vxlan_vni_delete_group(vxlan, vninode);
+	rhashtable_remove_fast(&vg->vni_hash, &vninode->vnode,
+			       vxlan_vni_rht_params);
+	__vxlan_vni_del_list(vg, vninode);
+	if (vxlan->dev->flags & IFF_UP)
+		vxlan_vs_add_del_vninode(vxlan, vninode, true);
+	call_rcu(&vninode->rcu, vxlan_vni_node_rcu_free);
 	return err;
 }
 
-- 
2.53.0