From patchwork Mon May 25 14:36:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 4967 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:788e:b0:861:c897:cb9d with SMTP id d14csp2474089max; Mon, 25 May 2026 07:36:39 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ9uMjMk03jfXyZTLVWLo34qP+pi7Hfg4Pg7nnmY3pSOo3bLuUPxta8lfmxWvPeAl53r12A5OtsyAek=@openvpn.net X-Received: by 2002:a05:6870:5116:b0:42c:1205:ef1 with SMTP id 586e51a60fabf-43b5adb0c7fmr8958763fac.25.1779719799354; Mon, 25 May 2026 07:36:39 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1779719799; cv=none; d=google.com; s=arc-20240605; b=i7zpm5k20PiYMgzecKLBgH4kj5csKqZNo0a9KZEdWq9fVDZhRnCf192aqwgi/eQ/91 W2Kq0IVJHosveykDLuPuFwOYWPWRppB8X9SjzmnVlQhev8xYAMFYPanwgxOrgnV7jjLM TvS9zEUxD7Iv75U6CMkzf4VuV+SHjgH0LTizLUdcEq1+Iox44Pg2rp1HxfIxln5I4T7N na6obN9YBOGyvhzn9LbvikHfNILuMcKQoeARC2H6mu4jd2Vni7Vk+gAJZrtDGa9yenBq iJflrqkNph1QVZ8HV7iK1TFVepYC2ACrshFIlyHbE+iXKBGzh9zyoMZuNbUQ9iTyjYmW 5qtQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:content-transfer-encoding:cc:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=1s0W5+KSDT8fh8DlDW/PDkMbvvigyXTsSDNp3IeXmQ4=; fh=BsMg/B0Yb/hS/rzP5Npz4luh0IleZm8REk1XWiWRt2A=; b=FDRgFgjse0CcUSdeM4bbDBdySHbvwYuJm6cHYscJjCuzfPUs2Sam5UcE+aYiSo57vZ yBYtWgIkQWrA2p2/MzTGfnd8s6+j5XxEd4Iues1vpMzxxs3O9mydA8CHXBqUVjMzHHJO MEN7E/G79ODKT5vi9ox7O9aqrahQFk0OnmIliCWb9z88+1LgYT/lKLMzzW53opX9bo7I f9pE5SqfCNvp+Ar2y8SKQ8To1qDVc+DoISSZNMBz94a7TAA2xbPVr2fgAU9NExgL72Ie ULVsRFpuRf1M25/WWqJ/VhgwAyMrolOeMfJoX8fbP7be2rrnCNY4JIY9UdkYY2MKPTz0 DJjg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=QpEyHlCa; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=crxvxFLl; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="X6MlQ/lU"; dkim=neutral (body hash did not verify) header.i=@unstable.cc header.s=MBO0001 header.b=GD5IwS7T; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-43b63bd5d19si8329238fac.214.2026.05.25.07.36.39 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 25 May 2026 07:36:39 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=QpEyHlCa; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=crxvxFLl; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="X6MlQ/lU"; dkim=neutral (body hash did not verify) header.i=@unstable.cc header.s=MBO0001 header.b=GD5IwS7T; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type:Cc: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=1s0W5+KSDT8fh8DlDW/PDkMbvvigyXTsSDNp3IeXmQ4=; b=QpEyHlCaqzSO6mJIohNOVZZ6+u SKYiSA49RVx8E83/nEa6pNuCsdv8a6GPYtF4Auei9c8oxi5t+o4ZH+VIjvQGyW2IMyzO49q0bi6Uk xLab1ML1GqHX5GY9kH8CNnZnS7NV3hvt9M8kSqUchGhbg/RpGHd9cN97+Ji7GB3a0xm8=; Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wRWPu-0001Cc-LB; Mon, 25 May 2026 14:36:31 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wRWPt-0001C0-2y for openvpn-devel@lists.sourceforge.net; Mon, 25 May 2026 14:36:29 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=3RJEfaEDNJg6717gaUTwcgxYlg32mZHgUWqZMEtXJOw=; b=crxvxFLloHk7FNrg9AwPbSjF0U iiq/w12yAJAwbvBZZPQtMWgf0+GEh6LUMz9yUSemKfUEsRds0EPLrpp77lTSTSV9s9qdpZewZRmF4 JQZBq63G+X9Yww+GyqwzzvIcIxo7glVmorub81wHaGaWK4vmLxODINgiijr6j2THwTTo=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=3RJEfaEDNJg6717gaUTwcgxYlg32mZHgUWqZMEtXJOw=; b=X6MlQ/lUYnaxju6ZC4Gxc+6KPV ogoys7N+BOSD5xFQTZamCIymf2PIy4+ex3eeks8h78rGwcRhDaJI6PrxjRM4d+pbybsEguBQ/lWYu 1BIbLAWNMACZml9w78E8/pITQRd363mwa4+1wILImF/bIOOj/dYfOnfXIZWMWkJVmv74=; Received: from mout-p-202.mailbox.org ([80.241.56.172]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wRWPm-0003pQ-8a for openvpn-devel@lists.sourceforge.net; Mon, 25 May 2026 14:36:26 +0000 Received: from smtp102.mailbox.org (smtp102.mailbox.org [10.196.197.102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-p-202.mailbox.org (Postfix) with ESMTPS id 4gPJN96HxTz9t9S; Mon, 25 May 2026 16:36:09 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=unstable.cc; s=MBO0001; t=1779719769; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3RJEfaEDNJg6717gaUTwcgxYlg32mZHgUWqZMEtXJOw=; b=GD5IwS7TlFnyudd9JW12QYSa4CTFKKgH8werKZ5E8ga4ovlVj+D79mGadOMHZhL4qyh2LM i1Th7/Fxr877hq4lDskYD9mTUghw/towGGWa62is5XySyFfBHeY6Y6wXdVkibUg+LrGTDg FVo/AiuWs+0E5JdMHSL8LqovFV38DHv2ZoJHQBJl/FjVcpUkEU+EUbH6H5DIdpR3LgoHp/ z289lnBhdPgKJ/IlsLSPXP9KYDzRU+xSyMTol/pwZZ3+HyTynPuEt0mYjSt+dML3fDjt5t /OT0QLNdi9QCYlI0yt5uXmJzyixAhSzSKhav92hS6Ar0ZykAxfBkufOVSrreWg== From: Antonio Quartulli To: openvpn-devel@lists.sourceforge.net Date: Mon, 25 May 2026 16:36:05 +0200 Message-ID: <20260525143606.1532168-3-a@unstable.cc> In-Reply-To: <20260525143606.1532168-1-a@unstable.cc> References: <20260525143606.1532168-1-a@unstable.cc> MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: From: Antonio Quartulli 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. Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid X-Headers-End: 1wRWPm-0003pQ-8a Subject: [Openvpn-devel] [PATCH 3/4] ovpn: cap number of ovpn devices per netns X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1866171468452194939 X-GMAIL-MSGID: 1866171468452194939 From: Antonio Quartulli 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 --- drivers/net/ovpn/main.c | 71 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 8 deletions(-) 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 #include #include +#include +#include #include #include @@ -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(); }