Commit 9ba74e6c authored by Eric Dumazet's avatar Eric Dumazet Committed by Jakub Kicinski
Browse files

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: default avatarEric Dumazet <edumazet@google.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent e5d75fc2
Loading
Loading
Loading
Loading
+1 −8
Original line number Diff line number Diff line
@@ -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;
+34 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/* 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 */
+9 −0
Original line number Diff line number Diff line
@@ -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.
+3 −0
Original line number Diff line number Diff line
@@ -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);