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 --- 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 #include #include +#include #include #include #include @@ -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] ||