Netdev List
 help / color / mirror / Atom feed
* [PATCH v2 net] l2tp: fix tunnel and session refcount leak on seq_file release
@ 2026-08-10 14:11 Eric Dumazet
  2026-08-11 10:20 ` Paolo Abeni
  0 siblings, 1 reply; 3+ messages in thread
From: Eric Dumazet @ 2026-08-10 14:11 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, netdev, eric.dumazet, Eric Dumazet,
	syzbot+d6fa74e3f19d6ee01e3a, James Chapman, Guillaume Nault

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>
---
 net/l2tp/l2tp_debugfs.c |  4 ++++
 net/l2tp/l2tp_ppp.c     | 51 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/net/l2tp/l2tp_debugfs.c b/net/l2tp/l2tp_debugfs.c
index b26986fda9d6cf096519d56032fe3deb5034c7c8..c12d5e1ee550ccc2608b6128cb653ca373280373 100644
--- a/net/l2tp/l2tp_debugfs.c
+++ b/net/l2tp/l2tp_debugfs.c
@@ -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);
diff --git a/net/l2tp/l2tp_ppp.c b/net/l2tp/l2tp_ppp.c
index f8881699e1cad88e6f312e6ca47ae0af94c6137c..1bcf447f2f8b08235fdec747d33fd188e869a382 100644
--- a/net/l2tp/l2tp_ppp.c
+++ b/net/l2tp/l2tp_ppp.c
@@ -1597,6 +1597,53 @@ static const struct seq_operations pppol2tp_seq_ops = {
 	.stop		= pppol2tp_seq_stop,
 	.show		= pppol2tp_seq_show,
 };
+
+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,
+};
 #endif /* CONFIG_PROC_FS */
 
 /*****************************************************************************
@@ -1608,8 +1655,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;
-- 
2.55.0.654.g21b8a5bc05-goog


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH v2 net] l2tp: fix tunnel and session refcount leak on seq_file release
  2026-08-10 14:11 [PATCH v2 net] l2tp: fix tunnel and session refcount leak on seq_file release Eric Dumazet
@ 2026-08-11 10:20 ` Paolo Abeni
  2026-08-11 10:59   ` Eric Dumazet
  0 siblings, 1 reply; 3+ messages in thread
From: Paolo Abeni @ 2026-08-11 10:20 UTC (permalink / raw)
  To: Eric Dumazet, David S . Miller, Jakub Kicinski
  Cc: Simon Horman, netdev, eric.dumazet, syzbot+d6fa74e3f19d6ee01e3a,
	James Chapman, Guillaume Nault

On 8/10/26 4:11 PM, Eric Dumazet wrote:
> @@ -1608,8 +1655,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);

It looks like the above does not build with CONFIG_PROC_FS=n:

net/l2tp/l2tp_ppp.c: In function ‘pppol2tp_init_net’:
net/l2tp/l2tp_ppp.c:1659:33: error: ‘pppol2tp_proc_ops’ undeclared (first use in this function); did you mean ‘pppol2tp_chan_ops’?
 1659 |                                &pppol2tp_proc_ops, net);
      |                                 ^~~~~~~~~~~~~~~~~
      |                                 pppol2tp_chan_ops

/P


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v2 net] l2tp: fix tunnel and session refcount leak on seq_file release
  2026-08-11 10:20 ` Paolo Abeni
@ 2026-08-11 10:59   ` Eric Dumazet
  0 siblings, 0 replies; 3+ messages in thread
From: Eric Dumazet @ 2026-08-11 10:59 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: David S . Miller, Jakub Kicinski, Simon Horman, netdev,
	eric.dumazet, syzbot+d6fa74e3f19d6ee01e3a, James Chapman,
	Guillaume Nault

On Tue, Aug 11, 2026 at 12:20 PM Paolo Abeni <pabeni@redhat.com> wrote:
>
> On 8/10/26 4:11 PM, Eric Dumazet wrote:
> > @@ -1608,8 +1655,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);
>
> It looks like the above does not build with CONFIG_PROC_FS=n:
>
> net/l2tp/l2tp_ppp.c: In function ‘pppol2tp_init_net’:
> net/l2tp/l2tp_ppp.c:1659:33: error: ‘pppol2tp_proc_ops’ undeclared (first use in this function); did you mean ‘pppol2tp_chan_ops’?
>  1659 |                                &pppol2tp_proc_ops, net);
>       |                                 ^~~~~~~~~~~~~~~~~
>       |                                 pppol2tp_chan_ops
>

For some reason, proc_create_net() is a NOP for CONFIG_PROC_FS=n, but
proc_create_data() is not.

Oh well.

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-11 10:59 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 14:11 [PATCH v2 net] l2tp: fix tunnel and session refcount leak on seq_file release Eric Dumazet
2026-08-11 10:20 ` Paolo Abeni
2026-08-11 10:59   ` Eric Dumazet

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox