[PATCH net v4 1/7] net: ip_gre: require CAP_NET_ADMIN in the device netns for changelink

From: Maoyi Xie

Date: Tue Jun 09 2026 - 12:43:21 EST


A tunnel changelink rewrites the tunnel in its creation netns. After an
IFLA_NET_NS_FD migration that netns is not the caller's. The rtnl
changelink path only checks CAP_NET_ADMIN against the caller's netns. A
caller with caps only in its current netns can then rewrite a tunnel
that lives in another netns, and it picks the endpoint addresses.

Add net_admin_capable(). It requires CAP_NET_ADMIN in the tunnel's netns
and is skipped when that netns is the device's current netns, where the
rtnl path already checked the cap. The other patches in this series use
the same helper.

Gate ipgre_changelink() and erspan_changelink() with it. The check is at
the top of the op, before any attribute is parsed, because the parsers
update live tunnel fields first. ipgre_netlink_parms() sets
t->collect_md before ip_tunnel_changelink() runs.

Commit 8b484efd5cb4 ("ip6: vti: Use ip6_tnl.net in
vti6_siocdevprivate().") added the same check on the ioctl path. This
adds it on RTM_NEWLINK.

Reported-by: Xiao Liang <shaw.leon@xxxxxxxxx>
Closes: https://lore.kernel.org/netdev/CABAhCOSzP1vaThGV35_VnsRCb=87_CPjPVsTHbq905k8A+BuUg@xxxxxxxxxxxxxx/
Fixes: d0f418516022 ("net, ip_tunnel: fix namespaces move")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Maoyi Xie <maoyixie.tju@xxxxxxxxx>
---
include/net/net_namespace.h | 18 ++++++++++++++++++
net/ipv4/ip_gre.c | 6 ++++++
2 files changed, 24 insertions(+)

diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 80de5e98a66d..17fb71a78cb6 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -358,6 +358,24 @@ static inline bool net_initialized(const struct net *net)
return READ_ONCE(net->list.next);
}

+/**
+ * net_admin_capable - test for CAP_NET_ADMIN over a network namespace
+ * @net: namespace whose state the operation would change
+ * @cur: namespace the operation runs in, e.g. dev_net(dev)
+ *
+ * Returns true when @net is @cur, where CAP_NET_ADMIN was already
+ * checked for the running namespace, or when the caller holds
+ * CAP_NET_ADMIN over @net. rtnl changelink paths use this: a device can
+ * be moved so its state lives in a namespace other than the one the
+ * request runs in, and the cap must then be held over that namespace.
+ */
+static inline bool net_admin_capable(const struct net *net,
+ const struct net *cur)
+{
+ return net_eq(net, cur) ||
+ ns_capable(net->user_ns, CAP_NET_ADMIN);
+}
+
static inline void __netns_tracker_alloc(struct net *net,
netns_tracker *tracker,
bool refcounted,
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 169e2921a851..040a0ef95184 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -1457,6 +1457,9 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
__u32 fwmark = t->fwmark;
int err;

+ if (!net_admin_capable(t->net, dev_net(dev)))
+ return -EPERM;
+
err = ipgre_newlink_encap_setup(dev, data);
if (err)
return err;
@@ -1486,6 +1489,9 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[],
__u32 fwmark = t->fwmark;
int err;

+ if (!net_admin_capable(t->net, dev_net(dev)))
+ return -EPERM;
+
err = ipgre_newlink_encap_setup(dev, data);
if (err)
return err;
--
2.34.1