l2tp: fix tunnel and session refcount leak on seq_file release

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: 0e0c3fee3a ("l2tp: hold reference on tunnels printed in pppol2tp proc file")
Fixes: f726214d9b ("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>
This commit is contained in:
Eric Dumazet
2026-08-11 14:46:51 +00:00
committed by Paolo Abeni
parent 120977e2c0
commit 9006c116dd
2 changed files with 57 additions and 3 deletions

View 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);

View 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
};
/*****************************************************************************