[Openvpn-devel,3/4] ovpn: cap number of ovpn devices per netns

Message ID 20260525143606.1532168-3-a@unstable.cc
State New
Headers show
Series [Openvpn-devel,1/4] ovpn: account user-triggered allocations to memcg | expand

Commit Message

Antonio Quartulli May 25, 2026, 2:36 p.m. UTC
From: Antonio Quartulli <antonio@openvpn.net>

The previous patches bound resource use within a single ovpn
interface, but a userns owner can still spin many MP-mode devices,
each costing ~128 KiB just for its peer hashtables.

Cap the number of ovpn netdevs per netns with a compile-time
constant (OVPN_MAX_DEVS, 256). The counter lives in a per-netns
struct registered via register_pernet_subsys() - the framework
zero-allocates and frees the storage, so no init/exit callbacks are
needed. ovpn_net_init() increments and rejects with -ENOSPC past
the cap; ovpn_net_uninit() decrements, keeping the counter balanced
across both ndo_init failure and normal teardown.

Also set dev->netns_immutable in ovpn_setup() to forbid moving an
ovpn device between netns. Without it, ndo_uninit would decrement
a different netns' counter than ndo_init incremented, eventually
wrapping the atomic_t and defeating the cap - a userns owner with
multiple child netns could trigger this on purpose.

A sysctl was rejected: net.* sysctls in a non-init netns are
writable by anyone with CAP_NET_ADMIN in that netns, i.e. the exact
actor this cap constrains. A compile-time constant cannot be raised
by an attacker; 256 is generous for any realistic deployment.

Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
 drivers/net/ovpn/main.c | 71 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 63 insertions(+), 8 deletions(-)

Patch

diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 425f4367f0ab..540ba89d81b7 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -14,6 +14,8 @@ 
 #include <linux/inetdevice.h>
 #include <net/gro_cells.h>
 #include <net/ip.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
 #include <net/rtnetlink.h>
 #include <uapi/linux/if_arp.h>
 
@@ -26,6 +28,29 @@ 
 #include "tcp.h"
 #include "udp.h"
 
+/* Hard cap on number of ovpn netdevs per network namespace. Pairs with the
+ * per-MP-instance OVPN_MAX_PEERS cap to bound total kernel memory an
+ * unprivileged userns owner can pin via repeated `ip link add ... type ovpn`
+ * calls (each MP-mode device costs ~128 KiB just for its peer hashtables).
+ *
+ * This is a compile-time constant on purpose: a sysctl in net.* would be
+ * writable by anyone with CAP_NET_ADMIN in the namespace - i.e. exactly the
+ * unprivileged userns owner this cap is meant to constrain - which would
+ * defeat the defence. 256 is generous for any realistic deployment.
+ */
+#define OVPN_MAX_DEVS 256
+
+struct ovpn_net {
+	atomic_t n_devs;
+};
+
+static unsigned int ovpn_net_id __read_mostly;
+
+static struct pernet_operations ovpn_pernet_ops = {
+	.id	= &ovpn_net_id,
+	.size	= sizeof(struct ovpn_net),
+};
+
 static void ovpn_priv_free(struct net_device *net)
 {
 	struct ovpn_priv *ovpn = netdev_priv(net);
@@ -74,27 +99,40 @@  static int ovpn_mp_alloc(struct ovpn_priv *ovpn)
 static int ovpn_net_init(struct net_device *dev)
 {
 	struct ovpn_priv *ovpn = netdev_priv(dev);
-	int err = gro_cells_init(&ovpn->gro_cells, dev);
+	struct ovpn_net *on = net_generic(dev_net(dev), ovpn_net_id);
+	int err;
+
+	if (atomic_fetch_inc(&on->n_devs) >= OVPN_MAX_DEVS) {
+		atomic_dec(&on->n_devs);
+		return -ENOSPC;
+	}
 
+	err = gro_cells_init(&ovpn->gro_cells, dev);
 	if (err < 0)
-		return err;
+		goto err_dec;
 
 	err = ovpn_mp_alloc(ovpn);
-	if (err < 0) {
-		gro_cells_destroy(&ovpn->gro_cells);
-		return err;
-	}
+	if (err < 0)
+		goto err_gro;
 
 	return 0;
+
+err_gro:
+	gro_cells_destroy(&ovpn->gro_cells);
+err_dec:
+	atomic_dec(&on->n_devs);
+	return err;
 }
 
 static void ovpn_net_uninit(struct net_device *dev)
 {
 	struct ovpn_priv *ovpn = netdev_priv(dev);
+	struct ovpn_net *on = net_generic(dev_net(dev), ovpn_net_id);
 
 	disable_delayed_work_sync(&ovpn->keepalive_work);
 	ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN);
 	gro_cells_destroy(&ovpn->gro_cells);
+	atomic_dec(&on->n_devs);
 }
 
 static const struct net_device_ops ovpn_netdev_ops = {
@@ -173,6 +211,14 @@  static void ovpn_setup(struct net_device *dev)
 	dev->needed_headroom = ALIGN(OVPN_HEAD_ROOM, 4);
 	dev->needed_tailroom = OVPN_MAX_PADDING;
 
+	/* forbid moving the device between network namespaces: ndo_init and
+	 * ndo_uninit are called in the originating and current netns
+	 * respectively, so a migrated device would dec a different netns'
+	 * n_devs counter than the one it incremented, eventually wrapping it
+	 * and defeating the per-netns cap.
+	 */
+	dev->netns_immutable = true;
+
 	SET_NETDEV_DEVTYPE(dev, &ovpn_type);
 }
 
@@ -233,13 +279,19 @@  static struct rtnl_link_ops ovpn_link_ops = {
 
 static int __init ovpn_init(void)
 {
-	int err = rtnl_link_register(&ovpn_link_ops);
+	int err = register_pernet_subsys(&ovpn_pernet_ops);
 
 	if (err) {
-		pr_err("ovpn: can't register rtnl link ops: %d\n", err);
+		pr_err("ovpn: can't register pernet ops: %d\n", err);
 		return err;
 	}
 
+	err = rtnl_link_register(&ovpn_link_ops);
+	if (err) {
+		pr_err("ovpn: can't register rtnl link ops: %d\n", err);
+		goto unreg_pernet;
+	}
+
 	err = ovpn_nl_register();
 	if (err) {
 		pr_err("ovpn: can't register netlink family: %d\n", err);
@@ -252,6 +304,8 @@  static int __init ovpn_init(void)
 
 unreg_rtnl:
 	rtnl_link_unregister(&ovpn_link_ops);
+unreg_pernet:
+	unregister_pernet_subsys(&ovpn_pernet_ops);
 	return err;
 }
 
@@ -259,6 +313,7 @@  static __exit void ovpn_cleanup(void)
 {
 	ovpn_nl_unregister();
 	rtnl_link_unregister(&ovpn_link_ops);
+	unregister_pernet_subsys(&ovpn_pernet_ops);
 
 	rcu_barrier();
 }