DORMANTno replies

[PATCH net] macsec: require CAP_NET_ADMIN in the device netns for changelink

From: Aamir Ahmed <hidden>
Date: 2026-09-05 21:20:58
Also in: lkml, stable
Subsystem: networking drivers, networking [macsec], the rest · Maintainers: Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Sabrina Dubroca, Linus Torvalds

macsec_changelink() can reach the driver of macsec->real_dev, but the
rtnl changelink path only checks CAP_NET_ADMIN against dev_net(dev), the
macsec device's own netns. That differs from dev_net(macsec->real_dev)
once the macsec device has been moved into another network namespace,
which is the ordinary way of handing a virtual interface to a container
while the real NIC stays behind.

Two paths in macsec_changelink() cross that boundary:

  - IFLA_MACSEC_OFFLOAD -> macsec_update_offload(), which calls
    ops->mdo_add_secy() / mdo_del_secy() on macsec->real_dev.
  - the tail "propagate to the device" block, which calls
    ops->mdo_upd_secy() on macsec->real_dev whenever the device is
    already offloaded and any other attribute changed. This one needs
    no IFLA_MACSEC_OFFLOAD attribute at all.

So a network namespace administrator with no capability in the real
device's namespace can toggle the real device's MACsec offload state and
push SecY parameters into its driver.

Reproduced with netdevsim, which implements NETIF_F_HW_MACSEC:

  # privileged setup in the initial netns
  echo "0 1" > /sys/bus/netdevsim/new_device
  ip link add link eni0np1 name macsec0 type macsec offload mac
  # macsec0 is then handed to a container: moved into an unprivileged
  # user+net namespace, while eni0np1 stays in the initial netns.

  # from inside that container, with no capability in the initial netns
  ip link set macsec0 type macsec offload off     # mdo_del_secy
  ip link set macsec0 type macsec encrypt off     # mdo_upd_secy

Both succeed, and the netdevsim driver in the initial netns logs the
corresponding SecY calls.

Gate the two boundary-crossing paths with rtnl_dev_link_net_capable(),
matching the "require CAP_NET_ADMIN in the device netns for changelink"
series applied to ip_gre, ip6_gre, ipip, ip_vti, ip6_vti, ip6_tunnel,
sit, xfrm_interface, geneve, vxlan and macvlan.

The macsec_is_offloaded() term is required: without it the mdo_upd_secy
path above stays open. The check is deliberately not applied to every
attribute, so that an administrator of the macsec device's own namespace
can still configure a non-offloaded device, whose attributes are local to
it; this follows macvlan, which likewise gates only the settings that
reach the lower device.

Scope: this closes the changelink vector only, which is the sole way to
turn the real device's MACsec offload on or off, and the only way to push
changed SecY parameters to it. Several ndo paths also reach real_dev's
driver as a side effect of the container operating its own device, and
remain gated only by CAP_NET_ADMIN in dev_net(dev), as they do for the
drivers in the series above: macsec_dev_open() / macsec_dev_stop(),
macsec_common_dellink(), and macsec_set_mac_address(), which calls
mdo_upd_secy() because the SCI is derived from the MAC. Those are a
separate question.

Fixes: 3cf3227a21d1 ("net: macsec: hardware offloading infrastructure")
Cc: stable@vger.kernel.org
Signed-off-by: Aamir Ahmed <redacted>
---
Found and tested with AI assistance (Claude Code). The bug was located by
auditing rtnl_link_ops.changelink handlers for the netns capability check
that ip_gre, ip6_gre, ipip, ip_vti, ip6_vti, ip6_tunnel, sit,
xfrm_interface, geneve, vxlan and macvlan recently gained; macsec and vlan
were the two that did not have it. The reproducer, the fix and the
before/after measurements below were run on a KASAN kernel under virtme-ng
with netdevsim as the offload-capable NIC.

Notes (not part of the commit message):

* Verified on netdevsim across three kernels (vulnerable / unconditional
  gate / this patch), with these cases:

    offloaded  + unpriv cross-netns "offload off"   -> denied
    offloaded  + unpriv cross-netns "encrypt off"   -> denied  (mdo_upd_secy)
    !offloaded + unpriv cross-netns "encrypt off"   -> allowed (local only)
    !offloaded + unpriv cross-netns "offload mac"   -> denied
    same-netns privileged "encrypt off"             -> allowed (no regression)
    privileged cross-netns offload toggle           -> allowed (no regression)

  An unconditional gate also denies the third case, which is a needless
  uAPI change; hence the narrower condition.

* rtnl_dev_link_net_capable() was introduced by the tunnel changelink
  series. Older stable trees may need that helper backported first, or
  the check open-coded as
    net_eq(dev_net(dev), dev_net(macsec->real_dev)) ||
    ns_capable(dev_net(macsec->real_dev)->user_ns, CAP_NET_ADMIN)

* VLAN has the same gap and is the last one in this class. Toggling
  VLAN_FLAG_GVRP/MVRP runs the GARP/MRP applicant on vlan->real_dev and
  transmits PDUs out of it. No driver callback is involved, so the impact
  is lower, but there the mutator is also reachable from the SIOCSIFVLAN
  SET_VLAN_FLAG_CMD ioctl, so the check has to go in
  vlan_dev_change_flags() rather than in the changelink handler. Sent
  separately so that the two can be reviewed independently.

  drivers/net/macsec.c | 8 ++++++++
  1 file changed, 8 insertions(+)
diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c
index 6f9f3aceff..0f351e839f 100644
--- a/drivers/net/macsec.c
+++ b/drivers/net/macsec.c
@@ -19,6 +19,7 @@
 #include <net/gro_cells.h>
 #include <net/macsec.h>
 #include <net/dst_metadata.h>
+#include <net/rtnetlink.h>
 #include <net/netdev_lock.h>
 #include <linux/phy.h>
 #include <linux/byteorder/generic.h>
@@ -3959,6 +3960,13 @@ static int macsec_changelink(struct net_device *dev, struct nlattr *tb[],
 	if (!data)
 		return 0;
 
+	if ((data[IFLA_MACSEC_OFFLOAD] || macsec_is_offloaded(macsec)) &&
+	    !rtnl_dev_link_net_capable(dev, dev_net(macsec->real_dev))) {
+		NL_SET_ERR_MSG(extack,
+			       "Changing a MACsec device whose real device is in another network namespace requires CAP_NET_ADMIN in that namespace");
+		return -EPERM;
+	}
+
 	if (data[IFLA_MACSEC_CIPHER_SUITE] ||
 	    data[IFLA_MACSEC_ICV_LEN] ||
 	    data[IFLA_MACSEC_SCI] ||
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help