From: Abdifatah Suruur <suruurism@gmail.com>
To: netdev@vger.kernel.org
Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, andrew+netdev@lunn.ch,
subash.a.kasiviswanathan@oss.qualcomm.com,
sean.tranchetti@oss.qualcomm.com, dnlplm@gmail.com,
linux-kernel@vger.kernel.org,
Abdifatah Suruur <suruurism@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH net v6] net: qualcomm: rmnet: require CAP_NET_ADMIN in the real device netns for config ops
Date: Wed, 9 Sep 2026 11:02:09 +0300 [thread overview]
Message-ID: <20260909080209.6348-1-suruurism@gmail.com> (raw)
An rmnet device may be created with its real device in a different
netns than the rmnet device itself (rmnet_newlink() resolves it in
link_net), and the config paths below only check CAP_NET_ADMIN against
dev_net(dev), while mutating rmnet port state attached to the real
device:
- rmnet_changelink() rewrites the endpoint mux table and
port->data_format and, via rmnet_vnd_update_dev_mtu(), can shrink the
MTU of the rmnet endpoint netdevs.
- rmnet_add_bridge() and rmnet_del_bridge(), reachable via
ndo_add_slave/ndo_del_slave through RTM_SETLINK IFLA_MASTER, flip
port->rmnet_mode and port->bridge_ep on the real device's port; with
bridge_ep pointing at a caller-owned device, rmnet_rx_handler() then
forwards real-device ingress frames to it.
- rmnet_set_coalesce() rewrites the port aggregation parameters via
ETHTOOL_SCOALESCE (ioctl) or ETHTOOL_MSG_COALESCE_SET (netlink),
whose capability checks likewise only cover dev's netns.
A caller privileged only in the rmnet device's netns can therefore
rewrite the shared cellular data-path state owned by another netns, and
steer its ingress traffic.
Gate the rtnl paths with rtnl_dev_link_net_capable(), matching the
"require CAP_NET_ADMIN in the device netns for changelink" series
(vxlan/geneve, CVE-2026-68432), and gate the ethtool setter with
ns_capable() in the real device netns, mirroring the check dev_ethtool()
already applies to dev's netns. Report the new rejections through
extack where one is available.
Fixes: 2abb5792387e ("net: qualcomm: rmnet: Allow configuration updates to existing devices")
Fixes: 60d58f971c1077 ("net: qualcomm: rmnet: Implement bridge mode")
Fixes: db8a563a9d9024 ("net: qualcomm: rmnet: add ethtool support for configuring tx aggregation")
Cc: stable@vger.kernel.org
Signed-off-by: Abdifatah Suruur <suruurism@gmail.com>
---
v6:
- gate rmnet_add_bridge() and rmnet_del_bridge() on slave_dev, the
RTM_SETLINK target the caller was actually authorized against,
instead of rmnet_dev: a master moved into the real device's netns
short-circuits rtnl_dev_link_net_capable() through the net_eq()
check, so a caller only privileged in the slave's netns could
otherwise attach or clear the bridge state of the real device's
port
- add the Fixes tag for the commit that introduced rmnet_set_coalesce()
v5:
- also gate rmnet_set_coalesce(), the ethtool setter that rewrites the
port aggregation parameters of the real device's port, per the
Sashiko review
- report the new capability rejections through extack where available
v4:
- use the netdev comment style, per Subash Abhinov Kasiviswanathan
v3:
- cover rmnet_add_bridge() and rmnet_del_bridge() with the same gate;
they mutate the same real-device port state via ndo_add_slave/
ndo_del_slave and have no capability check of their own
- correct the impact wording: rmnet_vnd_update_dev_mtu() only reads
real_dev->mtu; the MTU store lands on the rmnet endpoint netdevs via
rmnet_vnd_change_mtu(), not on the real device
v2:
- drop Reported-by: (implied for the author), per Jakub Kicinski
---
.../ethernet/qualcomm/rmnet/rmnet_config.c | 40 ++++++++++++++++++-
.../net/ethernet/qualcomm/rmnet/rmnet_vnd.c | 18 ++++++++-
2 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
index bed6f63facf25..8df9e455110dd 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
@@ -312,6 +312,16 @@ 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))) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Changing the port settings requires CAP_NET_ADMIN in the real device network namespace");
+ return -EPERM;
+ }
+
port = rmnet_get_port_rtnl(real_dev);
if (data[IFLA_RMNET_MUX_ID]) {
@@ -441,6 +451,19 @@ int rmnet_add_bridge(struct net_device *rmnet_dev,
struct rmnet_port *port, *slave_port;
int err;
+ /* The rtnl path authorizes the caller against the RTM_SETLINK
+ * target, slave_dev, but the port state mutated below is attached
+ * to real_dev, which may live in a different netns. Check the
+ * capability against slave_dev so the master's netns, which the
+ * caller was never checked against, cannot short-circuit the gate
+ * after being moved into the real device's netns.
+ */
+ if (!rtnl_dev_link_net_capable(slave_dev, dev_net(real_dev))) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Attaching a bridge device requires CAP_NET_ADMIN in the real device network namespace");
+ return -EPERM;
+ }
+
port = rmnet_get_port_rtnl(real_dev);
/* If there is more than one rmnet dev attached, its probably being
@@ -489,7 +512,22 @@ 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 authorizes the caller against the RTM_SETLINK
+ * target, slave_dev, but rmnet_unregister_bridge() below clears
+ * the bridge state of the real device's port, which may live in a
+ * different netns. Check the capability against slave_dev so the
+ * master's netns, which the caller was never checked against,
+ * cannot short-circuit the gate after being moved into the real
+ * device's netns.
+ */
+ if (!rtnl_dev_link_net_capable(slave_dev, dev_net(real_dev)))
+ return -EPERM;
+
+ port = rmnet_get_port_rtnl(slave_dev);
rmnet_unregister_bridge(port);
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
index 4f0ddcedfa979..1f4a3246f254a 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
@@ -4,9 +4,11 @@
* RMNET Data virtual network driver
*/
+#include <linux/capability.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/if_arp.h>
+#include <linux/netlink.h>
#include <net/pkt_sched.h>
#include "rmnet_config.h"
#include "rmnet_handlers.h"
@@ -240,9 +242,23 @@ static int rmnet_set_coalesce(struct net_device *dev,
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(dev);
+ struct net_device *real_dev = priv->real_dev;
struct rmnet_port *port;
- port = rmnet_get_port_rtnl(priv->real_dev);
+ /*
+ * The aggregation parameters live in the port attached to
+ * real_dev, which may reside in a different netns. The ethtool
+ * paths only require CAP_NET_ADMIN in dev's netns, so require it
+ * in real_dev's netns as well before mutating the shared port
+ * state.
+ */
+ if (!ns_capable(dev_net(real_dev)->user_ns, CAP_NET_ADMIN)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Changing aggregation parameters requires CAP_NET_ADMIN in the real device network namespace");
+ return -EPERM;
+ }
+
+ port = rmnet_get_port_rtnl(real_dev);
if (kernel_coal->tx_aggr_max_frames < 1 || kernel_coal->tx_aggr_max_frames > 64)
return -EINVAL;
--
2.53.0
next reply other threads:[~2026-09-09 8:02 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 8:02 Abdifatah Suruur [this message]
2026-09-10 8:05 ` [PATCH net v6] net: qualcomm: rmnet: require CAP_NET_ADMIN in the real device netns for config ops netdev-bot+sashiko
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=20260909080209.6348-1-suruurism@gmail.com \
--to=suruurism@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=dnlplm@gmail.com \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sean.tranchetti@oss.qualcomm.com \
--cc=stable@vger.kernel.org \
--cc=subash.a.kasiviswanathan@oss.qualcomm.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.