net: add networking namespace refcount tracker

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.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Eric Dumazet 2021-12-09 23:44:21 -08:00 committed by Jakub Kicinski
parent e5d75fc20b
commit 9ba74e6c9e
5 changed files with 65 additions and 8 deletions

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

@ -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

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