Skip to content

Commit

Permalink
net: add a refcount tracker for kernel sockets
Browse files Browse the repository at this point in the history
Commit ffa84b5 ("net: add netns refcount tracker to struct sock")
added a tracker to sockets, but did not track kernel sockets.

We still have syzbot reports hinting about netns being destroyed
while some kernel TCP sockets had not been dismantled.

This patch tracks kernel sockets, and adds a ref_tracker_dir_print()
call to net_free() right before the netns is freed.

Normally, each layer is responsible for properly releasing its
kernel sockets before last call to net_free().

This debugging facility is enabled with CONFIG_NET_NS_REFCNT_TRACKER=y

Signed-off-by: Eric Dumazet <[email protected]>
Reviewed-by: Kuniyuki Iwashima <[email protected]>
Tested-by: Kuniyuki Iwashima <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
Eric Dumazet authored and davem330 committed Oct 24, 2022
1 parent b29e0de commit 0cafd77
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 8 deletions.
30 changes: 22 additions & 8 deletions include/net/net_namespace.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ struct net {

struct ns_common ns;
struct ref_tracker_dir refcnt_tracker;

struct ref_tracker_dir notrefcnt_tracker; /* tracker for objects not
* refcounted against netns
*/
struct list_head dev_base_head;
struct proc_dir_entry *proc_net;
struct proc_dir_entry *proc_net_stat;
Expand Down Expand Up @@ -320,19 +322,31 @@ 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)
static inline void __netns_tracker_alloc(struct net *net,
netns_tracker *tracker,
bool refcounted,
gfp_t gfp)
{
#ifdef CONFIG_NET_NS_REFCNT_TRACKER
ref_tracker_alloc(&net->refcnt_tracker, tracker, gfp);
ref_tracker_alloc(refcounted ? &net->refcnt_tracker :
&net->notrefcnt_tracker,
tracker, gfp);
#endif
}

static inline void netns_tracker_free(struct net *net,
netns_tracker *tracker)
static inline void netns_tracker_alloc(struct net *net, netns_tracker *tracker,
gfp_t gfp)
{
__netns_tracker_alloc(net, tracker, true, gfp);
}

static inline void __netns_tracker_free(struct net *net,
netns_tracker *tracker,
bool refcounted)
{
#ifdef CONFIG_NET_NS_REFCNT_TRACKER
ref_tracker_free(&net->refcnt_tracker, tracker);
ref_tracker_free(refcounted ? &net->refcnt_tracker :
&net->notrefcnt_tracker, tracker);
#endif
}

Expand All @@ -346,7 +360,7 @@ static inline struct net *get_net_track(struct net *net,

static inline void put_net_track(struct net *net, netns_tracker *tracker)
{
netns_tracker_free(net, tracker);
__netns_tracker_free(net, tracker, true);
put_net(net);
}

Expand Down
5 changes: 5 additions & 0 deletions net/core/net_namespace.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)

refcount_set(&net->ns.count, 1);
ref_tracker_dir_init(&net->refcnt_tracker, 128);
ref_tracker_dir_init(&net->notrefcnt_tracker, 128);

refcount_set(&net->passive, 1);
get_random_bytes(&net->hash_mix, sizeof(u32));
Expand Down Expand Up @@ -429,6 +430,10 @@ static void net_free(struct net *net)
{
if (refcount_dec_and_test(&net->passive)) {
kfree(rcu_access_pointer(net->gen));

/* There should not be any trackers left there. */
ref_tracker_dir_exit(&net->notrefcnt_tracker);

kmem_cache_free(net_cachep, net);
}
}
Expand Down
14 changes: 14 additions & 0 deletions net/core/sock.c
Original file line number Diff line number Diff line change
Expand Up @@ -2094,6 +2094,9 @@ struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
if (likely(sk->sk_net_refcnt)) {
get_net_track(net, &sk->ns_tracker, priority);
sock_inuse_add(net, 1);
} else {
__netns_tracker_alloc(net, &sk->ns_tracker,
false, priority);
}

sock_net_set(sk, net);
Expand Down Expand Up @@ -2149,6 +2152,9 @@ static void __sk_destruct(struct rcu_head *head)

if (likely(sk->sk_net_refcnt))
put_net_track(sock_net(sk), &sk->ns_tracker);
else
__netns_tracker_free(sock_net(sk), &sk->ns_tracker, false);

sk_prot_free(sk->sk_prot_creator, sk);
}

Expand Down Expand Up @@ -2237,6 +2243,14 @@ struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority)
if (likely(newsk->sk_net_refcnt)) {
get_net_track(sock_net(newsk), &newsk->ns_tracker, priority);
sock_inuse_add(sock_net(newsk), 1);
} else {
/* Kernel sockets are not elevating the struct net refcount.
* Instead, use a tracker to more easily detect if a layer
* is not properly dismantling its kernel sockets at netns
* destroy time.
*/
__netns_tracker_alloc(sock_net(newsk), &newsk->ns_tracker,
false, priority);
}
sk_node_init(&newsk->sk_node);
sock_lock_init(newsk);
Expand Down
11 changes: 11 additions & 0 deletions net/netlink/af_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,17 @@ static int netlink_release(struct socket *sock)
}

sock_prot_inuse_add(sock_net(sk), &netlink_proto, -1);

/* Because struct net might disappear soon, do not keep a pointer. */
if (!sk->sk_net_refcnt && sock_net(sk) != &init_net) {
__netns_tracker_free(sock_net(sk), &sk->ns_tracker, false);
/* Because of deferred_put_nlk_sk and use of work queue,
* it is possible netns will be freed before this socket.
*/
sock_net_set(sk, &init_net);
__netns_tracker_alloc(&init_net, &sk->ns_tracker,
false, GFP_KERNEL);
}
call_rcu(&nlk->rcu, deferred_put_nlk_sk);
return 0;
}
Expand Down
3 changes: 3 additions & 0 deletions net/rds/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,9 @@ bool rds_tcp_tune(struct socket *sock)
release_sock(sk);
return false;
}
/* Update ns_tracker to current stack trace and refcounted tracker */
__netns_tracker_free(net, &sk->ns_tracker, false);

sk->sk_net_refcnt = 1;
netns_tracker_alloc(net, &sk->ns_tracker, GFP_KERNEL);
sock_inuse_add(net, 1);
Expand Down

0 comments on commit 0cafd77

Please sign in to comment.