Netdev List
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: suruurism@gmail.com
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	subash.a.kasiviswanathan@oss.qualcomm.com,
	sean.tranchetti@oss.qualcomm.com, horms@kernel.org,
	kuba@kernel.org
Subject: Re: [PATCH net v3] net: qualcomm: rmnet: require CAP_NET_ADMIN in the real device netns for config ops
Date: Fri, 04 Sep 2026 22:24:39 +0000	[thread overview]
Message-ID: <178856067967.219967.7550358106155720232@kernel.org> (raw)
In-Reply-To: <20260829170656.15548-1-suruurism@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 1 · Low: 2

- [Medium] Wrong reference netns in the new capability gate on the
  ndo_del_slave path.
- [Low] The two comments added above the new gates in rmnet_add_bridge()
  (lines 452-456) and rmnet_del_bridge() (lines 512-516) were copied…
- [Low] The new capability rejections return -EPERM silently.

--

net: qualcomm: rmnet: require CAP_NET_ADMIN in the real device netns for config ops

This adds rtnl_dev_link_net_capable() gates to rmnet_changelink(),
rmnet_add_bridge() and rmnet_del_bridge() so that a caller privileged only
in the rmnet device's netns can no longer rewrite the rmnet port state
attached to a real device living in another netns.  rmnet_del_bridge() also
grows local priv/real_dev lookups for the new check.

A few questions below.

> diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> index bed6f63facf2..6feafc55a7e9 100644
> --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> @@ -312,6 +312,14 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
>  	if (!rmnet_is_real_dev_registered(real_dev))
>  		return -ENODEV;
>  
> +	/*
> +	 * The rtnl path only checks CAP_NET_ADMIN against dev_net(dev),
> +	 * but the port state mutated below is attached to real_dev, which
> +	 * may live in a different netns.
> +	 */
> +	if (!rtnl_dev_link_net_capable(dev, dev_net(real_dev)))
> +		return -EPERM;
> +

[Severity: Low]
This isn't a bug, but should the new rejection report through extack?

Both rmnet_changelink() and rmnet_add_bridge() take a
struct netlink_ext_ack *extack, and every other error return in those two
functions fills it in:

	NL_SET_ERR_MSG_MOD(extack, "MUX ID already exists");
	NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
	NL_SET_ERR_MSG_MOD(extack, "more than one rmnet dev attached");
	NL_SET_ERR_MSG_MOD(extack, "more than one bridge dev attached");
	NL_SET_ERR_MSG_MOD(extack, "slave cannot be another rmnet dev");

The closest peer users of the same gate do report the reason, e.g. in
macvlan_changelink():

	    !rtnl_dev_link_net_capable(dev, dev_net(vlan->lowerdev))) {
		NL_SET_ERR_MSG(extack,
			       "Changing shared macvlan port settings requires CAP_NET_ADMIN in the lower device network namespace");
		return -EPERM;

Since the generic rtnetlink capability check also returns -EPERM for
RTM_NEWLINK/RTM_SETLINK, can user space tell the two denials apart as
written?

>  	port = rmnet_get_port_rtnl(real_dev);
>  
>  	if (data[IFLA_RMNET_MUX_ID]) {

[ ... ]

> @@ -441,6 +449,14 @@ int rmnet_add_bridge(struct net_device *rmnet_dev,
>  	struct rmnet_port *port, *slave_port;
>  	int err;
>  
> +	/*
> +	 * The rtnl path only checks CAP_NET_ADMIN against dev_net(dev),
> +	 * but the port state mutated below is attached to real_dev, which
> +	 * may live in a different netns.
> +	 */

[Severity: Low]
This isn't a bug, but the comment appears to have been copied from the
rmnet_changelink() hunk without adjusting it.

rmnet_add_bridge() and rmnet_del_bridge() have no dev parameter or local:

	int rmnet_add_bridge(struct net_device *rmnet_dev,
			     struct net_device *slave_dev,
			     struct netlink_ext_ack *extack)

	int rmnet_del_bridge(struct net_device *rmnet_dev,
			     struct net_device *slave_dev)

The rtnl entry point for these two is RTM_SETLINK IFLA_MASTER via
do_set_master() and ndo_add_slave/ndo_del_slave, where the caller was
checked against dev_net(slave_dev) rather than a changelink on dev.  Could
both comments name slave_dev instead of the non-existent dev?

Also, the three added blocks use the form with '/*' alone on the first
line, while the surrounding comments in this file put text on the first
line, e.g. just below in the same function:

	/* If there is more than one rmnet dev attached, its probably being
	 * used for muxing. Skip the briding in that case
	 */

> +	if (!rtnl_dev_link_net_capable(rmnet_dev, dev_net(real_dev)))
> +		return -EPERM;
> +
>  	port = rmnet_get_port_rtnl(real_dev);
>  
>  	/* If there is more than one rmnet dev attached, its probably being

[ ... ]

> @@ -489,7 +505,19 @@ int rmnet_add_bridge(struct net_device *rmnet_dev,
>  int rmnet_del_bridge(struct net_device *rmnet_dev,
>  		     struct net_device *slave_dev)
>  {
> -	struct rmnet_port *port = rmnet_get_port_rtnl(slave_dev);
> +	struct rmnet_priv *priv = netdev_priv(rmnet_dev);
> +	struct net_device *real_dev = priv->real_dev;
> +	struct rmnet_port *port;
> +
> +	/*
> +	 * The rtnl path only checks CAP_NET_ADMIN against dev_net(dev),
> +	 * but rmnet_unregister_bridge() below clears the bridge state of
> +	 * the real device's port, which may live in a different netns.
> +	 */
> +	if (!rtnl_dev_link_net_capable(rmnet_dev, dev_net(real_dev)))
> +		return -EPERM;

[Severity: Medium]
Is rmnet_dev the right reference device for the gate on the del path?

rtnl_dev_link_net_capable() short-circuits when the two namespaces are
equal:

net/core/rtnetlink.c:rtnl_dev_link_net_capable() {
	return net_eq(link_net, dev_net(dev)) ||
	       ns_capable(link_net->user_ns, CAP_NET_ADMIN);
}

On ndo_del_slave the netns the rtnl layer actually verified CAP_NET_ADMIN
against is dev_net(slave_dev), and rmnet_dev is simply whatever master is
already linked:

net/core/rtnetlink.c:do_set_master() {
	struct net_device *upper_dev = netdev_master_upper_dev_get(dev);
	...
		if (ops->ndo_del_slave) {
			err = ops->ndo_del_slave(upper_dev, dev);
	...
}

There is no netns-equality requirement on that master link, and nothing
seems to forbid moving the rmnet vnd device out of the slave's netns:
rmnet_vnd_setup() does not set dev->netns_immutable,
__dev_change_net_namespace() only refuses netns_immutable devices, and
rmnet_config_notify_cb() reacts to NETDEV_UNREGISTER only when
rmnet_is_real_dev_registered(dev) is true, which is false for the vnd
device, so moving rmnet_dev does not tear the bridge down.

If rmnet_dev ends up in real_dev's netns A while slave_dev stays in netns
B, then net_eq(dev_net(real_dev), dev_net(rmnet_dev)) is true, ns_capable()
is never evaluated, and a caller holding CAP_NET_ADMIN only in B can run
"ip link set <slave> nomaster" and reach:

drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c:rmnet_unregister_bridge() {
		real_port = rmnet_get_port_rtnl(real_dev);
		real_port->bridge_ep = NULL;
		real_port->rmnet_mode = RMNET_EPMODE_VND;
}

which is the cross-netns port mutation the commit message describes.
Reaching that state does need a prior CAP_NET_ADMIN-in-A action to move
rmnet_dev, so the window is narrow.

Would using slave_dev as the reference device on both bridge paths close
this?  On the add path the two are equivalent, since do_set_master()
resolves the new master with __dev_get_by_index(dev_net(slave_dev),
ifindex), so passing slave_dev there too would make the invariant
explicit.

> +
> +	port = rmnet_get_port_rtnl(slave_dev);
>  
>  	rmnet_unregister_bridge(port);
>

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260829170656.15548-1-suruurism%40gmail.com

      parent reply	other threads:[~2026-09-04 22:24 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-29 17:06 [PATCH net v3] net: qualcomm: rmnet: require CAP_NET_ADMIN in the real device netns for config ops user.email
2026-08-31 20:22 ` subash.a.kasiviswanathan
2026-08-31 20:47   ` Suruur
2026-09-01  6:27     ` subash.a.kasiviswanathan
2026-09-04 22:24 ` netdev-bot+sashiko [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178856067967.219967.7550358106155720232@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=sean.tranchetti@oss.qualcomm.com \
    --cc=subash.a.kasiviswanathan@oss.qualcomm.com \
    --cc=suruurism@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox