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();
 }
