Merge branch 'net-netns-refcount-tracking-base-series'

Eric Dumazet says:

====================
net: netns refcount tracking, base series

We have 100+ syzbot reports about netns being dismantled too soon,
still unresolved as of today.

We think a missing get_net() or an extra put_net() is the root cause.

In order to find the bug(s), and be able to spot future ones,
this patch adds CONFIG_NET_NS_REFCNT_TRACKER and new helpers
to precisely pair all put_net() with corresponding get_net().

To use these helpers, each data structure owning a refcount
should also use a "netns_tracker" to pair the get() and put().

Small sections of codes where the get()/put() are in sight
do not need to have a tracker, because they are short lived,
but in theory it is also possible to declare an on-stack tracker.

v2: Include core networking patches only.
====================

Link: https://lore.kernel.org/r/20211210074426.279563-1-eric.dumazet@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2021-12-10 06:38:30 -08:00
commit 3d20408dff
12 changed files with 102 additions and 23 deletions

View File

@ -173,6 +173,7 @@ struct channel {
spinlock_t downl; /* protects `chan', file.xq dequeue */ spinlock_t downl; /* protects `chan', file.xq dequeue */
struct ppp *ppp; /* ppp unit we're connected to */ struct ppp *ppp; /* ppp unit we're connected to */
struct net *chan_net; /* the net channel belongs to */ struct net *chan_net; /* the net channel belongs to */
netns_tracker ns_tracker;
struct list_head clist; /* link in list of channels per unit */ struct list_head clist; /* link in list of channels per unit */
rwlock_t upl; /* protects `ppp' and 'bridge' */ rwlock_t upl; /* protects `ppp' and 'bridge' */
struct channel __rcu *bridge; /* "bridged" ppp channel */ struct channel __rcu *bridge; /* "bridged" ppp channel */
@ -2879,7 +2880,7 @@ int ppp_register_net_channel(struct net *net, struct ppp_channel *chan)
pch->ppp = NULL; pch->ppp = NULL;
pch->chan = chan; pch->chan = chan;
pch->chan_net = get_net(net); pch->chan_net = get_net_track(net, &pch->ns_tracker, GFP_KERNEL);
chan->ppp = pch; chan->ppp = pch;
init_ppp_file(&pch->file, CHANNEL); init_ppp_file(&pch->file, CHANNEL);
pch->file.hdrlen = chan->hdrlen; pch->file.hdrlen = chan->hdrlen;
@ -3519,7 +3520,7 @@ ppp_disconnect_channel(struct channel *pch)
*/ */
static void ppp_destroy_channel(struct channel *pch) static void ppp_destroy_channel(struct channel *pch)
{ {
put_net(pch->chan_net); put_net_track(pch->chan_net, &pch->ns_tracker);
pch->chan_net = NULL; pch->chan_net = NULL;
atomic_dec(&channel_count); atomic_dec(&channel_count);

View File

@ -61,15 +61,27 @@ static int seq_open_net(struct inode *inode, struct file *file)
} }
#ifdef CONFIG_NET_NS #ifdef CONFIG_NET_NS
p->net = net; p->net = net;
netns_tracker_alloc(net, &p->ns_tracker, GFP_KERNEL);
#endif #endif
return 0; return 0;
} }
static void seq_file_net_put_net(struct seq_file *seq)
{
#ifdef CONFIG_NET_NS
struct seq_net_private *priv = seq->private;
put_net_track(priv->net, &priv->ns_tracker);
#else
put_net(&init_net);
#endif
}
static int seq_release_net(struct inode *ino, struct file *f) static int seq_release_net(struct inode *ino, struct file *f)
{ {
struct seq_file *seq = f->private_data; struct seq_file *seq = f->private_data;
put_net(seq_file_net(seq)); seq_file_net_put_net(seq);
seq_release_private(ino, f); seq_release_private(ino, f);
return 0; return 0;
} }
@ -87,7 +99,8 @@ int bpf_iter_init_seq_net(void *priv_data, struct bpf_iter_aux_info *aux)
#ifdef CONFIG_NET_NS #ifdef CONFIG_NET_NS
struct seq_net_private *p = priv_data; struct seq_net_private *p = priv_data;
p->net = get_net(current->nsproxy->net_ns); p->net = get_net_track(current->nsproxy->net_ns, &p->ns_tracker,
GFP_KERNEL);
#endif #endif
return 0; return 0;
} }
@ -97,7 +110,7 @@ void bpf_iter_fini_seq_net(void *priv_data)
#ifdef CONFIG_NET_NS #ifdef CONFIG_NET_NS
struct seq_net_private *p = priv_data; struct seq_net_private *p = priv_data;
put_net(p->net); put_net_track(p->net, &p->ns_tracker);
#endif #endif
} }

View File

@ -48,7 +48,7 @@
#include <uapi/linux/pkt_cls.h> #include <uapi/linux/pkt_cls.h>
#include <linux/hashtable.h> #include <linux/hashtable.h>
#include <linux/rbtree.h> #include <linux/rbtree.h>
#include <linux/ref_tracker.h> #include <net/net_trackers.h>
struct netpoll_info; struct netpoll_info;
struct device; struct device;
@ -300,13 +300,6 @@ enum netdev_state_t {
__LINK_STATE_TESTING, __LINK_STATE_TESTING,
}; };
#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
typedef struct ref_tracker *netdevice_tracker;
#else
typedef struct {} netdevice_tracker;
#endif
struct gro_list { struct gro_list {
struct list_head list; struct list_head list;
int count; int count;

View File

@ -10,6 +10,7 @@ extern struct net init_net;
struct seq_net_private { struct seq_net_private {
#ifdef CONFIG_NET_NS #ifdef CONFIG_NET_NS
struct net *net; struct net *net;
netns_tracker ns_tracker;
#endif #endif
}; };

View File

@ -34,6 +34,7 @@
#include <net/netns/smc.h> #include <net/netns/smc.h>
#include <net/netns/bpf.h> #include <net/netns/bpf.h>
#include <net/netns/mctp.h> #include <net/netns/mctp.h>
#include <net/net_trackers.h>
#include <linux/ns_common.h> #include <linux/ns_common.h>
#include <linux/idr.h> #include <linux/idr.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
@ -87,6 +88,7 @@ struct net {
struct idr netns_ids; struct idr netns_ids;
struct ns_common ns; struct ns_common ns;
struct ref_tracker_dir refcnt_tracker;
struct list_head dev_base_head; struct list_head dev_base_head;
struct proc_dir_entry *proc_net; struct proc_dir_entry *proc_net;
@ -240,6 +242,7 @@ void ipx_unregister_sysctl(void);
#ifdef CONFIG_NET_NS #ifdef CONFIG_NET_NS
void __put_net(struct net *net); void __put_net(struct net *net);
/* Try using get_net_track() instead */
static inline struct net *get_net(struct net *net) static inline struct net *get_net(struct net *net)
{ {
refcount_inc(&net->ns.count); refcount_inc(&net->ns.count);
@ -258,6 +261,7 @@ static inline struct net *maybe_get_net(struct net *net)
return net; return net;
} }
/* Try using put_net_track() instead */
static inline void put_net(struct net *net) static inline void put_net(struct net *net)
{ {
if (refcount_dec_and_test(&net->ns.count)) if (refcount_dec_and_test(&net->ns.count))
@ -308,6 +312,36 @@ static inline int check_net(const struct net *net)
#endif #endif
static inline void netns_tracker_alloc(struct net *net,
netns_tracker *tracker, gfp_t gfp)
{
#ifdef CONFIG_NET_NS_REFCNT_TRACKER
ref_tracker_alloc(&net->refcnt_tracker, tracker, gfp);
#endif
}
static inline void netns_tracker_free(struct net *net,
netns_tracker *tracker)
{
#ifdef CONFIG_NET_NS_REFCNT_TRACKER
ref_tracker_free(&net->refcnt_tracker, tracker);
#endif
}
static inline struct net *get_net_track(struct net *net,
netns_tracker *tracker, gfp_t gfp)
{
get_net(net);
netns_tracker_alloc(net, tracker, gfp);
return net;
}
static inline void put_net_track(struct net *net, netns_tracker *tracker)
{
netns_tracker_free(net, tracker);
put_net(net);
}
typedef struct { typedef struct {
#ifdef CONFIG_NET_NS #ifdef CONFIG_NET_NS
struct net *net; struct net *net;

View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __NET_NET_TRACKERS_H
#define __NET_NET_TRACKERS_H
#include <linux/ref_tracker.h>
#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
typedef struct ref_tracker *netdevice_tracker;
#else
typedef struct {} netdevice_tracker;
#endif
#ifdef CONFIG_NET_NS_REFCNT_TRACKER
typedef struct ref_tracker *netns_tracker;
#else
typedef struct {} netns_tracker;
#endif
#endif /* __NET_NET_TRACKERS_H */

View File

@ -203,6 +203,7 @@ struct tcf_exts {
int nr_actions; int nr_actions;
struct tc_action **actions; struct tc_action **actions;
struct net *net; struct net *net;
netns_tracker ns_tracker;
#endif #endif
/* Map to export classifier specific extension TLV types to the /* Map to export classifier specific extension TLV types to the
* generic extensions API. Unsupported extensions must be set to 0. * generic extensions API. Unsupported extensions must be set to 0.
@ -218,6 +219,7 @@ static inline int tcf_exts_init(struct tcf_exts *exts, struct net *net,
exts->type = 0; exts->type = 0;
exts->nr_actions = 0; exts->nr_actions = 0;
exts->net = net; exts->net = net;
netns_tracker_alloc(net, &exts->ns_tracker, GFP_KERNEL);
exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *), exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
GFP_KERNEL); GFP_KERNEL);
if (!exts->actions) if (!exts->actions)
@ -236,6 +238,8 @@ static inline bool tcf_exts_get_net(struct tcf_exts *exts)
{ {
#ifdef CONFIG_NET_CLS_ACT #ifdef CONFIG_NET_CLS_ACT
exts->net = maybe_get_net(exts->net); exts->net = maybe_get_net(exts->net);
if (exts->net)
netns_tracker_alloc(exts->net, &exts->ns_tracker, GFP_KERNEL);
return exts->net != NULL; return exts->net != NULL;
#else #else
return true; return true;
@ -246,7 +250,7 @@ static inline void tcf_exts_put_net(struct tcf_exts *exts)
{ {
#ifdef CONFIG_NET_CLS_ACT #ifdef CONFIG_NET_CLS_ACT
if (exts->net) if (exts->net)
put_net(exts->net); put_net_track(exts->net, &exts->ns_tracker);
#endif #endif
} }

View File

@ -350,6 +350,7 @@ struct bpf_local_storage;
* @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME * @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME
* @sk_txtime_report_errors: set report errors mode for SO_TXTIME * @sk_txtime_report_errors: set report errors mode for SO_TXTIME
* @sk_txtime_unused: unused txtime flags * @sk_txtime_unused: unused txtime flags
* @ns_tracker: tracker for netns reference
*/ */
struct sock { struct sock {
/* /*
@ -538,6 +539,7 @@ struct sock {
struct bpf_local_storage __rcu *sk_bpf_storage; struct bpf_local_storage __rcu *sk_bpf_storage;
#endif #endif
struct rcu_head sk_rcu; struct rcu_head sk_rcu;
netns_tracker ns_tracker;
}; };
enum sk_pacing { enum sk_pacing {

View File

@ -8,3 +8,12 @@ config NET_DEV_REFCNT_TRACKER
help help
Enable debugging feature to track device references. Enable debugging feature to track device references.
This adds memory and cpu costs. This adds memory and cpu costs.
config NET_NS_REFCNT_TRACKER
bool "Enable networking namespace refcount tracking"
depends on DEBUG_KERNEL && STACKTRACE_SUPPORT
select REF_TRACKER
default n
help
Enable debugging feature to track netns references.
This adds memory and cpu costs.

View File

@ -311,6 +311,8 @@ static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
LIST_HEAD(net_exit_list); LIST_HEAD(net_exit_list);
refcount_set(&net->ns.count, 1); refcount_set(&net->ns.count, 1);
ref_tracker_dir_init(&net->refcnt_tracker, 128);
refcount_set(&net->passive, 1); refcount_set(&net->passive, 1);
get_random_bytes(&net->hash_mix, sizeof(u32)); get_random_bytes(&net->hash_mix, sizeof(u32));
preempt_disable(); preempt_disable();
@ -635,6 +637,7 @@ static DECLARE_WORK(net_cleanup_work, cleanup_net);
void __put_net(struct net *net) void __put_net(struct net *net)
{ {
ref_tracker_dir_exit(&net->refcnt_tracker);
/* Cleanup the network namespace in process context */ /* Cleanup the network namespace in process context */
if (llist_add(&net->cleanup_list, &cleanup_list)) if (llist_add(&net->cleanup_list, &cleanup_list))
queue_work(netns_wq, &net_cleanup_work); queue_work(netns_wq, &net_cleanup_work);

View File

@ -1983,7 +1983,7 @@ struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
sock_lock_init(sk); sock_lock_init(sk);
sk->sk_net_refcnt = kern ? 0 : 1; sk->sk_net_refcnt = kern ? 0 : 1;
if (likely(sk->sk_net_refcnt)) { if (likely(sk->sk_net_refcnt)) {
get_net(net); get_net_track(net, &sk->ns_tracker, priority);
sock_inuse_add(net, 1); sock_inuse_add(net, 1);
} }
@ -2039,7 +2039,7 @@ static void __sk_destruct(struct rcu_head *head)
put_pid(sk->sk_peer_pid); put_pid(sk->sk_peer_pid);
if (likely(sk->sk_net_refcnt)) if (likely(sk->sk_net_refcnt))
put_net(sock_net(sk)); put_net_track(sock_net(sk), &sk->ns_tracker);
sk_prot_free(sk->sk_prot_creator, sk); sk_prot_free(sk->sk_prot_creator, sk);
} }
@ -2126,7 +2126,7 @@ struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority)
/* SANITY */ /* SANITY */
if (likely(newsk->sk_net_refcnt)) { if (likely(newsk->sk_net_refcnt)) {
get_net(sock_net(newsk)); get_net_track(sock_net(newsk), &newsk->ns_tracker, priority);
sock_inuse_add(sock_net(newsk), 1); sock_inuse_add(sock_net(newsk), 1);
} }
sk_node_init(&newsk->sk_node); sk_node_init(&newsk->sk_node);

View File

@ -33,6 +33,7 @@ static struct dentry *rootdir;
struct l2tp_dfs_seq_data { struct l2tp_dfs_seq_data {
struct net *net; struct net *net;
netns_tracker ns_tracker;
int tunnel_idx; /* current tunnel */ int tunnel_idx; /* current tunnel */
int session_idx; /* index of session within current tunnel */ int session_idx; /* index of session within current tunnel */
struct l2tp_tunnel *tunnel; struct l2tp_tunnel *tunnel;
@ -281,7 +282,7 @@ static int l2tp_dfs_seq_open(struct inode *inode, struct file *file)
rc = PTR_ERR(pd->net); rc = PTR_ERR(pd->net);
goto err_free_pd; goto err_free_pd;
} }
netns_tracker_alloc(pd->net, &pd->ns_tracker, GFP_KERNEL);
rc = seq_open(file, &l2tp_dfs_seq_ops); rc = seq_open(file, &l2tp_dfs_seq_ops);
if (rc) if (rc)
goto err_free_net; goto err_free_net;
@ -293,7 +294,7 @@ out:
return rc; return rc;
err_free_net: err_free_net:
put_net(pd->net); put_net_track(pd->net, &pd->ns_tracker);
err_free_pd: err_free_pd:
kfree(pd); kfree(pd);
goto out; goto out;
@ -307,7 +308,7 @@ static int l2tp_dfs_seq_release(struct inode *inode, struct file *file)
seq = file->private_data; seq = file->private_data;
pd = seq->private; pd = seq->private;
if (pd->net) if (pd->net)
put_net(pd->net); put_net_track(pd->net, &pd->ns_tracker);
kfree(pd); kfree(pd);
seq_release(inode, file); seq_release(inode, file);