]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
l2tp: fix tunnel and session refcount leak on seq_file release
authorEric Dumazet <edumazet@google.com>
Tue, 11 Aug 2026 14:46:51 +0000 (14:46 +0000)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 13 Aug 2026 09:49:31 +0000 (11:49 +0200)
In pppol2tp_proc_open() and l2tp_dfs_seq_open(), iteration state
(pd->tunnel and pd->session) is kept in seq_file private data to allow
iteration across multiple read() system calls.

However, if userspace closes /proc/net/pppol2tp or /sys/kernel/debug/l2tp/tunnels
before reading to end-of-file (EOF), any tunnel or session reference stored in
pd->tunnel / pd->session is left un-dropped when seq_file private data is freed.

Fix this by dropping any remaining pd->tunnel and pd->session references in
pppol2tp_proc_release() and l2tp_dfs_seq_release() when closing the file.

Fixes: 0e0c3fee3a59 ("l2tp: hold reference on tunnels printed in pppol2tp proc file")
Fixes: f726214d9b23 ("l2tp: hold reference on tunnels printed in l2tp/tunnels debugfs file")
Reported-by: syzbot+d6fa74e3f19d6ee01e3a@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/6a760f32.01d0871a.3a0d52.004f.GAE@google.com/T/#u
Assisted-by: Jetski:Gemini-3.1-Pro
Cc: James Chapman <jchapman@katalix.com>
Cc: Guillaume Nault <gnault@redhat.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260811144651.2733424-1-edumazet@google.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
net/l2tp/l2tp_debugfs.c
net/l2tp/l2tp_ppp.c

index b26986fda9d6cf096519d56032fe3deb5034c7c8..c12d5e1ee550ccc2608b6128cb653ca373280373 100644 (file)
@@ -306,6 +306,10 @@ static int l2tp_dfs_seq_release(struct inode *inode, struct file *file)
 
        seq = file->private_data;
        pd = seq->private;
+       if (pd->session)
+               l2tp_session_put(pd->session);
+       if (pd->tunnel)
+               l2tp_tunnel_put(pd->tunnel);
        if (pd->net)
                put_net_track(pd->net, &pd->ns_tracker);
        kfree(pd);
index f8881699e1cad88e6f312e6ca47ae0af94c6137c..ef8fdfaf051d4602691fbd82e164e3b1eb31a86b 100644 (file)
@@ -1597,7 +1597,53 @@ static const struct seq_operations pppol2tp_seq_ops = {
        .stop           = pppol2tp_seq_stop,
        .show           = pppol2tp_seq_show,
 };
-#endif /* CONFIG_PROC_FS */
+
+static int pppol2tp_proc_open(struct inode *inode, struct file *file)
+{
+       struct net *net = pde_data(inode);
+       struct pppol2tp_seq_data *pd;
+
+       net = maybe_get_net(net);
+       if (!net)
+               return -ENXIO;
+
+       pd = __seq_open_private(file, &pppol2tp_seq_ops, sizeof(*pd));
+       if (!pd) {
+               put_net(net);
+               return -ENOMEM;
+       }
+
+#ifdef CONFIG_NET_NS
+       pd->p.net = net;
+       netns_tracker_alloc(net, &pd->p.ns_tracker, GFP_KERNEL);
+#endif
+       return 0;
+}
+
+static int pppol2tp_proc_release(struct inode *inode, struct file *file)
+{
+       struct seq_file *seq = file->private_data;
+       struct pppol2tp_seq_data *pd = seq->private;
+
+       if (pd->session)
+               l2tp_session_put(pd->session);
+       if (pd->tunnel)
+               l2tp_tunnel_put(pd->tunnel);
+
+#ifdef CONFIG_NET_NS
+       put_net_track(pd->p.net, &pd->p.ns_tracker);
+#else
+       put_net(&init_net);
+#endif
+       return seq_release_private(inode, file);
+}
+
+static const struct proc_ops pppol2tp_proc_ops = {
+       .proc_open      = pppol2tp_proc_open,
+       .proc_read      = seq_read,
+       .proc_lseek     = seq_lseek,
+       .proc_release   = pppol2tp_proc_release,
+};
 
 /*****************************************************************************
  * Network namespace
@@ -1608,8 +1654,8 @@ static __net_init int pppol2tp_init_net(struct net *net)
        struct proc_dir_entry *pde;
        int err = 0;
 
-       pde = proc_create_net("pppol2tp", 0444, net->proc_net,
-                             &pppol2tp_seq_ops, sizeof(struct pppol2tp_seq_data));
+       pde = proc_create_data("pppol2tp", 0444, net->proc_net,
+                              &pppol2tp_proc_ops, net);
        if (!pde) {
                err = -ENOMEM;
                goto out;
@@ -1624,9 +1670,13 @@ static __net_exit void pppol2tp_exit_net(struct net *net)
        remove_proc_entry("pppol2tp", net->proc_net);
 }
 
+#endif /* CONFIG_PROC_FS */
+
 static struct pernet_operations pppol2tp_net_ops = {
+#ifdef CONFIG_PROC_FS
        .init = pppol2tp_init_net,
        .exit = pppol2tp_exit_net,
+#endif
 };
 
 /*****************************************************************************