mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
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:
commit
3d20408dff
@ -173,6 +173,7 @@ struct channel {
|
||||
spinlock_t downl; /* protects `chan', file.xq dequeue */
|
||||
struct ppp *ppp; /* ppp unit we're connected 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 */
|
||||
rwlock_t upl; /* protects `ppp' and 'bridge' */
|
||||
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->chan = chan;
|
||||
pch->chan_net = get_net(net);
|
||||
pch->chan_net = get_net_track(net, &pch->ns_tracker, GFP_KERNEL);
|
||||
chan->ppp = pch;
|
||||
init_ppp_file(&pch->file, CHANNEL);
|
||||
pch->file.hdrlen = chan->hdrlen;
|
||||
@ -3519,7 +3520,7 @@ ppp_disconnect_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;
|
||||
|
||||
atomic_dec(&channel_count);
|
||||
|
@ -61,15 +61,27 @@ static int seq_open_net(struct inode *inode, struct file *file)
|
||||
}
|
||||
#ifdef CONFIG_NET_NS
|
||||
p->net = net;
|
||||
netns_tracker_alloc(net, &p->ns_tracker, GFP_KERNEL);
|
||||
#endif
|
||||
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)
|
||||
{
|
||||
struct seq_file *seq = f->private_data;
|
||||
|
||||
put_net(seq_file_net(seq));
|
||||
seq_file_net_put_net(seq);
|
||||
seq_release_private(ino, f);
|
||||
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
|
||||
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
|
||||
return 0;
|
||||
}
|
||||
@ -97,7 +110,7 @@ void bpf_iter_fini_seq_net(void *priv_data)
|
||||
#ifdef CONFIG_NET_NS
|
||||
struct seq_net_private *p = priv_data;
|
||||
|
||||
put_net(p->net);
|
||||
put_net_track(p->net, &p->ns_tracker);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@
|
||||
#include <uapi/linux/pkt_cls.h>
|
||||
#include <linux/hashtable.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/ref_tracker.h>
|
||||
#include <net/net_trackers.h>
|
||||
|
||||
struct netpoll_info;
|
||||
struct device;
|
||||
@ -300,13 +300,6 @@ enum netdev_state_t {
|
||||
__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 list_head list;
|
||||
int count;
|
||||
|
@ -9,7 +9,8 @@ extern struct net init_net;
|
||||
|
||||
struct seq_net_private {
|
||||
#ifdef CONFIG_NET_NS
|
||||
struct net *net;
|
||||
struct net *net;
|
||||
netns_tracker ns_tracker;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <net/netns/smc.h>
|
||||
#include <net/netns/bpf.h>
|
||||
#include <net/netns/mctp.h>
|
||||
#include <net/net_trackers.h>
|
||||
#include <linux/ns_common.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/skbuff.h>
|
||||
@ -87,6 +88,7 @@ struct net {
|
||||
struct idr netns_ids;
|
||||
|
||||
struct ns_common ns;
|
||||
struct ref_tracker_dir refcnt_tracker;
|
||||
|
||||
struct list_head dev_base_head;
|
||||
struct proc_dir_entry *proc_net;
|
||||
@ -240,6 +242,7 @@ void ipx_unregister_sysctl(void);
|
||||
#ifdef CONFIG_NET_NS
|
||||
void __put_net(struct net *net);
|
||||
|
||||
/* Try using get_net_track() instead */
|
||||
static inline struct net *get_net(struct net *net)
|
||||
{
|
||||
refcount_inc(&net->ns.count);
|
||||
@ -258,6 +261,7 @@ static inline struct net *maybe_get_net(struct net *net)
|
||||
return net;
|
||||
}
|
||||
|
||||
/* Try using put_net_track() instead */
|
||||
static inline void put_net(struct net *net)
|
||||
{
|
||||
if (refcount_dec_and_test(&net->ns.count))
|
||||
@ -308,6 +312,36 @@ static inline int check_net(const struct net *net)
|
||||
#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 {
|
||||
#ifdef CONFIG_NET_NS
|
||||
struct net *net;
|
||||
|
18
include/net/net_trackers.h
Normal file
18
include/net/net_trackers.h
Normal 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 */
|
@ -202,7 +202,8 @@ struct tcf_exts {
|
||||
__u32 type; /* for backward compat(TCA_OLD_COMPAT) */
|
||||
int nr_actions;
|
||||
struct tc_action **actions;
|
||||
struct net *net;
|
||||
struct net *net;
|
||||
netns_tracker ns_tracker;
|
||||
#endif
|
||||
/* Map to export classifier specific extension TLV types to the
|
||||
* 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->nr_actions = 0;
|
||||
exts->net = net;
|
||||
netns_tracker_alloc(net, &exts->ns_tracker, GFP_KERNEL);
|
||||
exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
|
||||
GFP_KERNEL);
|
||||
if (!exts->actions)
|
||||
@ -236,6 +238,8 @@ static inline bool tcf_exts_get_net(struct tcf_exts *exts)
|
||||
{
|
||||
#ifdef CONFIG_NET_CLS_ACT
|
||||
exts->net = maybe_get_net(exts->net);
|
||||
if (exts->net)
|
||||
netns_tracker_alloc(exts->net, &exts->ns_tracker, GFP_KERNEL);
|
||||
return exts->net != NULL;
|
||||
#else
|
||||
return true;
|
||||
@ -246,7 +250,7 @@ static inline void tcf_exts_put_net(struct tcf_exts *exts)
|
||||
{
|
||||
#ifdef CONFIG_NET_CLS_ACT
|
||||
if (exts->net)
|
||||
put_net(exts->net);
|
||||
put_net_track(exts->net, &exts->ns_tracker);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -350,6 +350,7 @@ struct bpf_local_storage;
|
||||
* @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME
|
||||
* @sk_txtime_report_errors: set report errors mode for SO_TXTIME
|
||||
* @sk_txtime_unused: unused txtime flags
|
||||
* @ns_tracker: tracker for netns reference
|
||||
*/
|
||||
struct sock {
|
||||
/*
|
||||
@ -538,6 +539,7 @@ struct sock {
|
||||
struct bpf_local_storage __rcu *sk_bpf_storage;
|
||||
#endif
|
||||
struct rcu_head sk_rcu;
|
||||
netns_tracker ns_tracker;
|
||||
};
|
||||
|
||||
enum sk_pacing {
|
||||
|
@ -8,3 +8,12 @@ config NET_DEV_REFCNT_TRACKER
|
||||
help
|
||||
Enable debugging feature to track device references.
|
||||
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.
|
||||
|
@ -311,6 +311,8 @@ static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
|
||||
LIST_HEAD(net_exit_list);
|
||||
|
||||
refcount_set(&net->ns.count, 1);
|
||||
ref_tracker_dir_init(&net->refcnt_tracker, 128);
|
||||
|
||||
refcount_set(&net->passive, 1);
|
||||
get_random_bytes(&net->hash_mix, sizeof(u32));
|
||||
preempt_disable();
|
||||
@ -635,6 +637,7 @@ static DECLARE_WORK(net_cleanup_work, cleanup_net);
|
||||
|
||||
void __put_net(struct net *net)
|
||||
{
|
||||
ref_tracker_dir_exit(&net->refcnt_tracker);
|
||||
/* Cleanup the network namespace in process context */
|
||||
if (llist_add(&net->cleanup_list, &cleanup_list))
|
||||
queue_work(netns_wq, &net_cleanup_work);
|
||||
|
@ -1983,7 +1983,7 @@ struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
|
||||
sock_lock_init(sk);
|
||||
sk->sk_net_refcnt = kern ? 0 : 1;
|
||||
if (likely(sk->sk_net_refcnt)) {
|
||||
get_net(net);
|
||||
get_net_track(net, &sk->ns_tracker, priority);
|
||||
sock_inuse_add(net, 1);
|
||||
}
|
||||
|
||||
@ -2039,7 +2039,7 @@ static void __sk_destruct(struct rcu_head *head)
|
||||
put_pid(sk->sk_peer_pid);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -2126,7 +2126,7 @@ struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority)
|
||||
|
||||
/* SANITY */
|
||||
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);
|
||||
}
|
||||
sk_node_init(&newsk->sk_node);
|
||||
|
@ -32,7 +32,8 @@
|
||||
static struct dentry *rootdir;
|
||||
|
||||
struct l2tp_dfs_seq_data {
|
||||
struct net *net;
|
||||
struct net *net;
|
||||
netns_tracker ns_tracker;
|
||||
int tunnel_idx; /* current tunnel */
|
||||
int session_idx; /* index of session within current 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);
|
||||
goto err_free_pd;
|
||||
}
|
||||
|
||||
netns_tracker_alloc(pd->net, &pd->ns_tracker, GFP_KERNEL);
|
||||
rc = seq_open(file, &l2tp_dfs_seq_ops);
|
||||
if (rc)
|
||||
goto err_free_net;
|
||||
@ -293,7 +294,7 @@ out:
|
||||
return rc;
|
||||
|
||||
err_free_net:
|
||||
put_net(pd->net);
|
||||
put_net_track(pd->net, &pd->ns_tracker);
|
||||
err_free_pd:
|
||||
kfree(pd);
|
||||
goto out;
|
||||
@ -307,7 +308,7 @@ static int l2tp_dfs_seq_release(struct inode *inode, struct file *file)
|
||||
seq = file->private_data;
|
||||
pd = seq->private;
|
||||
if (pd->net)
|
||||
put_net(pd->net);
|
||||
put_net_track(pd->net, &pd->ns_tracker);
|
||||
kfree(pd);
|
||||
seq_release(inode, file);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user