All of lore.kernel.org
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: kuniyu@google.com
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, dsahern@kernel.org,
	idosch@nvidia.com, horms@kernel.org, kuni1840@gmail.com,
	netdev@vger.kernel.org, razor@blackwall.org
Subject: Re: [PATCH v6 net-next 10/15] neighbour: Clean up neigh_table_init() and neigh_table_clear().
Date: Fri, 04 Sep 2026 03:10:00 +0000	[thread overview]
Message-ID: <178849140082.4131868.5096987849252659627@kernel.org> (raw)
In-Reply-To: <20260902203722.926528-11-kuniyu@google.com>

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 1 · Medium: 0 · Low: 0 (1)

- [High] net/core/neighbour.c:neigh_table_clear() now reclaims the RCU-
  published neighbour hash table synchronously: `nht =…

Pre-existing issues:
- [Low] neigh_table_clear() (net/core/neighbour.c:1881-1906) frees table-
  wide resources (free_percpu(tbl->stats) at 1895,…

--

neighbour: Clean up neigh_table_init() and neigh_table_clear().

This reworks neigh_table_init() to allocate everything up front and unwind
on failure, and reorders neigh_table_clear() to mirror it. The teardown
side also switches to timer_shutdown_sync(), drops the explicit
pneigh_queue_purge(), replaces pr_crit() with DEBUG_NET_WARN_ON_ONCE(), and
calls neigh_hash_free_rcu() directly instead of via call_rcu().

> diff --git a/net/core/neighbour.c b/net/core/neighbour.c
> index 34da4cdc813d..36488dbd1512 100644
> --- a/net/core/neighbour.c
> +++ b/net/core/neighbour.c

[ ... neigh_table_init() hunk snipped ... ]

> @@ -1869,26 +1880,27 @@ void neigh_table_init(struct neigh_table *tbl)
>   */
>  int neigh_table_clear(struct neigh_table *tbl)
>  {
> -	/* It is not clean... Fix it to unload IPv6 module safely */
> +	struct net *net __maybe_unused = &init_net;
> +	struct neigh_hash_table *nht;
> +
>  	cancel_delayed_work_sync(&tbl->managed_work);
>  	cancel_delayed_work_sync(&tbl->gc_work);
> -	timer_delete_sync(&tbl->proxy_timer);
> -	pneigh_queue_purge(&tbl->proxy_queue, NULL, tbl->family);
> +	timer_shutdown_sync(&tbl->proxy_timer);
> +
>  	neigh_ifdown(tbl, NULL);
> -	if (atomic_read(&tbl->entries))
> -		pr_crit("neighbour leakage\n");
> +	DEBUG_NET_WARN_ON_ONCE(atomic_read(&tbl->entries));

[Severity: Low]

This isn't a bug introduced by this patch, but the diagnostic swap makes a
pre-existing lifecycle problem invisible on production kernels: with
CONFIG_DEBUG_NET=n, DEBUG_NET_WARN_ON_ONCE() compiles to nothing, so the
"still have entries" condition that previously always printed is no longer
reported.

The table-owned resources are still freed unconditionally just below, and a
neighbour destroyed afterwards touches them:

net/core/neighbour.c:neigh_destroy() {
	NEIGH_CACHE_STAT_INC(neigh->tbl, destroys);
	...
	atomic_dec(&neigh->tbl->entries);
}

The unconditional freeing itself is unchanged from the baseline, and the
next patch in the series, "neighbour: Convert neigh_table.entries to
refcount_t", defers freeing of tbl->stats, tbl->phash_buckets and tbl->nht
into neigh_table_free() once the last reference is dropped.

Is dropping the production-visible message intentional for this
intermediate commit?

>  
> -	call_rcu(&rcu_dereference_protected(tbl->nht, 1)->rcu,
> -		 neigh_hash_free_rcu);
> -	tbl->nht = NULL;
> +	remove_proc_entry(tbl->id, net->proc_net_stat);
> +
> +	free_percpu(tbl->stats);
> +	tbl->stats = NULL;
>  
>  	kfree(tbl->phash_buckets);
>  	tbl->phash_buckets = NULL;
>  
> -	remove_proc_entry(tbl->id, init_net.proc_net_stat);
> -
> -	free_percpu(tbl->stats);
> -	tbl->stats = NULL;
> +	nht = rcu_dereference_protected(tbl->nht, 1);
> +	tbl->nht = NULL;
> +	neigh_hash_free_rcu(&nht->rcu);

[Severity: High]

Can this free the neighbour hash table while lockless readers still hold a
pointer to it?

neigh_hash_free_rcu() is an RCU callback body, so calling it directly
releases both the bucket array and the table object immediately:

net/core/neighbour.c:neigh_hash_free_rcu() {
	...
	kfree(nht->hash_heads);
	kfree(nht);
}

Nothing in the new teardown sequence waits for a grace period:
cancel_delayed_work_sync() twice, timer_shutdown_sync(), neigh_ifdown()
and remove_proc_entry(). neigh_ifdown() only takes tbl->lock, which the
readers never take.

The readers dereference tbl->nht under RCU with no NULL check:

include/net/neighbour.h:___neigh_lookup_noref() {
	struct neigh_hash_table *nht = rcu_dereference(tbl->nht);
	...
	hash_val = hash(pkey, dev, nht->hash_rnd) >> (32 - nht->hash_shift);
	neigh_for_each_in_bucket_rcu(n, &nht->hash_heads[hash_val])
}

so a reader that already loaded nht reads hash_shift, hash_rnd and
hash_heads out of freed slab memory and then follows whatever hlist
contents are left there. neigh_lookup(), neigh_dump_table(),
neigh_seq_start() and neightbl_fill_info() have the same shape.

Is the table still published while this runs? ndisc_cleanup() calls
neigh_table_clear(&nd_tbl) before unregister_pernet_subsys(&ndisc_net_ops),
and the pernet exit path is what clears net->neigh_tables[NEIGH_ND_TABLE],
so nd_tbl remains installed in every netns during the free. An
unprivileged RTM_GETNEIGH dump reaches it holding only rcu_read_lock():

net/core/neighbour.c:neigh_dump_info() {
	rcu_read_lock();
	for (t = 0; t < NEIGH_NR_TABLES; t++) {
		tbl = net->neigh_tables[t];

and ndisc_cleanup() is live today from the module-init error unwind:

net/ipv6/af_inet6.c:inet6_init() {
igmp_fail:
	ndisc_cleanup();

The peer site retiring the same object type still uses a grace period:

net/core/neighbour.c:neigh_hash_grow() {
	rcu_assign_pointer(tbl->nht, new_nht);
	call_rcu(&old_nht->rcu, neigh_hash_free_rcu);
}

Would keeping call_rcu()/kfree_rcu(), or inserting a synchronize_net()
before the free, be preferable here? The plain NULL store into the __rcu
pointer tbl->nht predates this patch; what is new is the access to freed
memory. This also does not appear to be addressed later in the series: at
the end of the range the same unsynchronised free lives in
neigh_table_free(), now additionally doing kfree(tbl), while
neigh_table_unregister() clears net->neigh_tables[index] and calls
neigh_table_clear()/neigh_table_put() with no grace period in between.

>  
>  	return 0;
>  }

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902203722.926528-1-kuniyu%40google.com

  reply	other threads:[~2026-09-04  3:10 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02 20:36 [PATCH v6 net-next 00/15] neighbour: Namespacify arp_tbl and nd_tbl Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 01/15] selftest: net: Deflake Periodic GC test in test_neigh.sh Kuniyuki Iwashima
2026-09-04  3:09   ` netdev-bot+sashiko
2026-09-02 20:36 ` [PATCH v6 net-next 02/15] neighbour: Remove __neigh_for_each_release() Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 03/15] neighbour: Remove lock dance for neigh_update_{gc,managed}_list() Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 04/15] neighbour: Remove unnecessary EXPORT_SYMBOL() Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 05/15] neighbour: Remove __rcu from neigh_tables[] Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 06/15] neighbour: Store arp_tbl and nd_tbl in net->neigh_tables[] Kuniyuki Iwashima
2026-09-04  3:09   ` netdev-bot+sashiko
2026-09-02 20:36 ` [PATCH v6 net-next 07/15] neighbour: Remove neigh_tables[] Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 08/15] ipv4: Replace &arp_tbl with arp_table(net) Kuniyuki Iwashima
2026-09-04  3:09   ` netdev-bot+sashiko
2026-09-04 16:16     ` Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 09/15] ipv6: Replace &nd_tbl with nd_table(net) Kuniyuki Iwashima
2026-09-04  3:09   ` netdev-bot+sashiko
2026-09-04 16:36     ` Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 10/15] neighbour: Clean up neigh_table_init() and neigh_table_clear() Kuniyuki Iwashima
2026-09-04  3:10   ` netdev-bot+sashiko [this message]
2026-09-04 16:28     ` Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 11/15] neighbour: Convert neigh_table.entries to refcount_t Kuniyuki Iwashima
2026-09-04  3:10   ` netdev-bot+sashiko
2026-09-04 16:39     ` Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 12/15] neighbour: Namespacify neigh_tables Kuniyuki Iwashima
2026-09-04  3:10   ` netdev-bot+sashiko
2026-09-04 16:44     ` Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 13/15] neighbour: Don't store net in struct pneigh_entry Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 14/15] neighbour: Remove unnecessary net_eq() Kuniyuki Iwashima
2026-09-02 20:36 ` [PATCH v6 net-next 15/15] selftest: net: Specify netns for ip ntable in test_neigh.sh Kuniyuki Iwashima
2026-09-04  3:10   ` netdev-bot+sashiko
2026-09-04 16:46     ` Kuniyuki Iwashima
2026-09-03 12:46 ` [PATCH v6 net-next 00/15] neighbour: Namespacify arp_tbl and nd_tbl Ido Schimmel
2026-09-04 22:20 ` patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178849140082.4131868.5096987849252659627@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=dsahern@kernel.org \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=idosch@nvidia.com \
    --cc=kuba@kernel.org \
    --cc=kuni1840@gmail.com \
    --cc=kuniyu@google.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=razor@blackwall.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.