[PATCH RFC v3 85/90] tun: ignore an equal uid or gid across cells

From: Josef Bacik

Date: Thu Nov 05 2026 - 10:40:49 EST


A tun device can have an owner and a group, set with TUNSETOWNER and
TUNSETGROUP. tun_permitted() lets a task attach to the device without
CAP_NET_ADMIN if its euid is the owner, or it is a member of the group, or
both if both are set.

That check does not know about cells. A one-id cell runs with the uid and
gid of the user who built it. If the cell has /dev/net/tun in its tree and
uses a network namespace in which that user owns tun devices, its tasks
pass the check for all of them. They can attach, read the packets and
inject their own, with no capability and no right over the network.

A cell with a block of ids has a related problem. Blocks are reused, and a
persistent tun device can outlive the cell that its owner belonged to. A
later cell that is lent the same block would pass the check.

Fix both the way it is done for keys. Record the id of the cell that the
caller of TUNSETOWNER or TUNSETGROUP is in, and honour the owner and the
group only for tasks of that cell. Cell ids are never reused. Any other
task needs NET_RIGHT_TUN, as it does for a device that belongs to somebody
else.

As a consequence, an owner that a builder sets from outside of the cell no
longer lets a task of the cell attach. The builder can hand the device to
the task already open, or give the cell CELL_NET_TUN.

Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
drivers/net/tun.c | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 0a2f864a253e..ef1cc5617812 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -47,6 +47,7 @@
#include <linux/miscdevice.h>
#include <linux/ethtool.h>
#include <linux/rtnetlink.h>
+#include <linux/cell.h>
#include <linux/compat.h>
#include <linux/if.h>
#include <linux/if_arp.h>
@@ -185,6 +186,8 @@ struct tun_struct {
unsigned int flags;
kuid_t owner;
kgid_t group;
+ u64 owner_cell; /* the cell that @owner is to be read in */
+ u64 group_cell; /* and @group */

struct net_device *dev;
netdev_features_t set_features;
@@ -518,6 +521,14 @@ static inline bool tun_permitted(struct tun_struct *tun)
const struct cred *cred = current_cred();
struct net *net = dev_net(tun->dev);

+ /*
+ * Across cells an equal uid, or gid, means nothing: a cell of one id runs on
+ * those of its maker, and a block of ids is lent to one cell after another.
+ */
+ if ((uid_valid(tun->owner) && tun->owner_cell != cred_cell_id(cred)) ||
+ (gid_valid(tun->group) && tun->group_cell != cred_cell_id(cred)))
+ return net_may(net, NET_RIGHT_TUN);
+
/* If both uid and gid are set, *both* must match */
if ((uid_valid(tun->owner) || gid_valid(tun->group)) &&
(!uid_valid(tun->owner) || uid_eq(cred->euid, tun->owner)) &&
@@ -3338,6 +3349,7 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
break;
}
WRITE_ONCE(tun->owner, owner);
+ tun->owner_cell = current_cell_id();
do_notify = true;
netif_info(tun, drv, tun->dev, "owner set to %u\n",
from_kuid(&init_user_ns, owner));
@@ -3351,6 +3363,7 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
break;
}
WRITE_ONCE(tun->group, group);
+ tun->group_cell = current_cell_id();
do_notify = true;
netif_info(tun, drv, tun->dev, "group set to %u\n",
from_kgid(&init_user_ns, group));

--
2.55.0