Netdev List
 help / color / mirror / Atom feed
* Re: [REGRESSION][BISECTED] stmmac: suspend hangs since 1b9707e6f1a9 ("net: stmmac: enable RPS and RBU interrupts")
From: tresonic @ 2026-07-17 22:34 UTC (permalink / raw)
  To: Andrew Lunn; +Cc: netdev, regressions, rmk+kernel, kuba, Maxime Chevallier
In-Reply-To: <8f6aca6d-6619-401c-bed7-8d42732d166a@mail.de>

>> But i'm also having trouble finding where dwmac4_disable_dma_irq() is
>> actually called. It is called from stmmac_disable_rx_queue() and
>> stmmac_disable_tx_queue() but they only seem to be used in the XDP
>> code.

Afaict these are called through the stmmac_stop_rx macro which maps to the function pointers in dwmac410_dma_ops.
So this actually gets called from stmmac_suspend via stmmac_stop_all_dma.

^ permalink raw reply

* [PATCH] net/mlx5e: ktls: guard RX resync against missing TLS context
From: Rishikesh Jethwani @ 2026-07-17 22:46 UTC (permalink / raw)
  To: netdev
  Cc: john.fastabend, kuba, sd, davem, pabeni, edumazet, leon,
	nils.juenemann, borisp, saeedm, tariqt, mbloch, shshitrit,
	Rishikesh Jethwani

resync_update_sn() handles CQE_TLS_OFFLOAD_RESYNC by looking up the
socket with inet_lookup_established() / __inet6_lookup_established()
and then dereferencing tls_get_ctx(sk) on the assumption that the
returned socket owns the TLS context that produced the CQE.

That assumption is not guaranteed. The established lookup matches only
on the 5-tuple and does not filter on TLS ULP state, so it can return a
TCP_ESTABLISHED socket with no TLS context attached. In that case
tls_get_ctx(sk) is NULL and the RX resync path dereferences it.

In the observed crash, the returned socket was TCP_ESTABLISHED with both
icsk_ulp_ops and icsk_ulp_data NULL, i.e. a socket without a TLS ULP
attached at lookup time. This can happen if a stale resync CQE is
matched to a different socket for the same 5-tuple, or otherwise
resolves to a socket without a TLS context.

Fetch tls_get_ctx(sk) once after the TIME_WAIT check and bail out if it
is NULL, then pass the sampled tls_context down to the resync helpers.

Fixes: 0419d8c9d8f8 ("net/mlx5e: kTLS, Add kTLS RX resync support")
Link: https://lore.kernel.org/netdev/20260705104419.4014-1-nils.juenemann@gmail.com/
Reported-by: Nils Juenemann <nils.juenemann@gmail.com>
Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
---
 .../mellanox/mlx5/core/en_accel/ktls_rx.c       | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_rx.c
index bca45679e201..a1cab11a07dc 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_rx.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_rx.c
@@ -471,12 +471,12 @@ void mlx5e_ktls_handle_get_psv_completion(struct mlx5e_icosq_wqe_info *wi,
 /* Runs in NAPI.
  * Function elevates the refcount, unless no work is queued.
  */
-static bool resync_queue_get_psv(struct sock *sk)
+static bool resync_queue_get_psv(struct tls_context *tls_ctx)
 {
 	struct mlx5e_ktls_offload_context_rx *priv_rx;
 	struct mlx5e_ktls_rx_resync_ctx *resync;
 
-	priv_rx = mlx5e_get_ktls_rx_priv_ctx(tls_get_ctx(sk));
+	priv_rx = mlx5e_get_ktls_rx_priv_ctx(tls_ctx);
 	if (unlikely(!priv_rx))
 		return false;
 
@@ -500,6 +500,7 @@ static void resync_update_sn(struct mlx5e_rq *rq, struct sk_buff *skb)
 	struct tls_offload_resync_async *resync_async;
 	struct net_device *netdev = rq->netdev;
 	struct net *net = dev_net(netdev);
+	struct tls_context *tls_ctx;
 	struct sock *sk = NULL;
 	unsigned int datalen;
 	struct iphdr *iph;
@@ -538,12 +539,20 @@ static void resync_update_sn(struct mlx5e_rq *rq, struct sk_buff *skb)
 	if (unlikely(sk->sk_state == TCP_TIME_WAIT))
 		goto unref;
 
-	if (unlikely(!resync_queue_get_psv(sk)))
+	/* Established lookup is tuple-based and may return a socket without
+	 * a TLS ULP attached. Sample the TLS context once and bail out if
+	 * none is present.
+	 */
+	tls_ctx = tls_get_ctx(sk);
+	if (unlikely(!tls_ctx))
+		goto unref;
+
+	if (unlikely(!resync_queue_get_psv(tls_ctx)))
 		goto unref;
 
 	seq = th->seq;
 	datalen = skb->len - depth;
-	resync_async = tls_offload_ctx_rx(tls_get_ctx(sk))->resync_async;
+	resync_async = tls_offload_ctx_rx(tls_ctx)->resync_async;
 	tls_offload_rx_resync_async_request_start(resync_async, seq, datalen);
 	rq->stats->tls_resync_req_start++;
 
-- 
2.25.1


^ permalink raw reply related

* Re: [REGRESSION][BISECTED] stmmac: suspend hangs since 1b9707e6f1a9 ("net: stmmac: enable RPS and RBU interrupts")
From: Andrew Lunn @ 2026-07-18  0:16 UTC (permalink / raw)
  To: tresonic; +Cc: netdev, regressions, rmk+kernel, kuba, Maxime Chevallier
In-Reply-To: <8f6aca6d-6619-401c-bed7-8d42732d166a@mail.de>

> It seems your assumptions are correct. If i do
> 
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
> index a0249715fafa..487efc746b20 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
> @@ -129,6 +129,8 @@ void dwmac4_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
>         const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
>         u32 value = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
>  
> +       value &= ~DMA_CHAN_INTR_ENA_RPS;
> +
>         if (rx)
>                 value &= ~DMA_CHAN_INTR_ENA_RIE;
>         if (tx)
> 
> I can suspend and resume!

Cool. So we might be going in the correct direction.

> Could it be sufficient to just do this and reenable on dwmac4_enable_dma_irq?

Unfortunately, it is not as simple as that.

The driver implements NAPI. Interrupts are expensive, so what NAPI
does is after there is an interrupt indicating there are received
packets, and there are more than 64 packets to be received, it
disables interrupts, and goes into polling made. Whenever it polls, if
there are packets available it keeps on polling. Only when polling
indicates there are no more packets, are interrupts re-enabled and
polling stopped.

You can see parts of this logic in stmmac_napi_poll_rx().

This means it cannot be done here. We need a function which is only
called on suspend, and probably release.

The interrupt is being enabled in the init_chan call in
stmmac_dma_ops. Ideally, it should be disabled in a mirror function,
which currently does not exist. So maybe deinit_chan() needs
adding. But where to call it from? init_chan() is called from
stmmac_init_dma_engine(), from stmmac_hw_setup(). stmmac_resume() does
call this. So we need something in stmmac_suspend(). Maybe in
stmmac_stop_all_dma()?

stmmac is messy, there are often not mirror functions. If there is a
stmmac_init_dma_engine() there should be
stmmac_deinit_dma_engine(). If there is stmmac_hw_setup() there should
be stmmac_hw_tairdown(). But none of these seem to exist. 

Anyway, do you want to try to implement deinit_chan() and call it from
stmmac_stop_all_dma()?

   Andrew

^ permalink raw reply

* 答复: [外部邮件] Re: [PATCH] net: ipv6: fix a potential use-after-free in ip4ip6_err
From: Li,Rongqing @ 2026-07-18  0:43 UTC (permalink / raw)
  To: Xin Long
  Cc: David Ahern, Ido Schimmel, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <CADvbK_cEUR9m-QyKi5jj2MTqF2kGbBj7d1mUXmCsHgDLNJ-bPQ@mail.gmail.com>



> -----邮件原件-----
> 发件人: Xin Long <lucien.xin@gmail.com>
> 发送时间: 2026年7月17日 23:53
> 收件人: Li,Rongqing <lirongqing@baidu.com>
> 抄送: David Ahern <dsahern@kernel.org>; Ido Schimmel
> <idosch@nvidia.com>; David S . Miller <davem@davemloft.net>; Eric
> Dumazet <edumazet@google.com>; Jakub Kicinski <kuba@kernel.org>; Paolo
> Abeni <pabeni@redhat.com>; Simon Horman <horms@kernel.org>;
> netdev@vger.kernel.org; linux-kernel@vger.kernel.org
> 主题: [外部邮件] Re: [PATCH] net: ipv6: fix a potential use-after-free in
> ip4ip6_err
> 
> On Fri, Jul 17, 2026 at 10:33 AM lirongqing <lirongqing@baidu.com> wrote:
> >
> > From: Li RongQing <lirongqing@baidu.com>
> >
> > Fix a use-after-free bug in ip4ip6_err() where rt->rt_flags is
> > accessed after the route entry object has been released via ip_rt_put(rt).
> >
> > If ip_rt_put() decrements the reference count to zero and frees the
> > rtable structure, reading rt->rt_flags immediately afterward results
> > in a use-after-free pointer dereference.
> >
> > Fix this by caching rt->rt_flags into a local variable before calling
> > ip_rt_put().
> >
> > Fixes: 77552cfa39c4 ("ip6_tunnel: clean up ip4ip6 and ip6ip6's
> > err_handlers")
> > Signed-off-by: Li RongQing <lirongqing@baidu.com>
> > ---
> >  net/ipv6/ip6_tunnel.c | 4 +++-
> >  1 file changed, 3 insertions(+), 1 deletion(-)
> >
> > diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index
> > bf8e40a..984cb0c 100644
> > --- a/net/ipv6/ip6_tunnel.c
> > +++ b/net/ipv6/ip6_tunnel.c
> > @@ -569,6 +569,7 @@ ip4ip6_err(struct sk_buff *skb, struct
> > inet6_skb_parm *opt,  {
> >         __u32 rel_info = ntohl(info);
> >         const struct iphdr *eiph;
> > +       unsigned int rt_flags;
> >         struct sk_buff *skb2;
> >         int err, rel_msg = 0;
> >         u8 rel_type = type;
> > @@ -627,10 +628,11 @@ ip4ip6_err(struct sk_buff *skb, struct
> inet6_skb_parm *opt,
> >                 goto out;
> >
> >         skb2->dev = rt->dst.dev;
> > +       rt_flags = rt->rt_flags;
> >         ip_rt_put(rt);
> >
> >         /* route "incoming" packet */
> > -       if (rt->rt_flags & RTCF_LOCAL) {
> > +       if (rt_flags & RTCF_LOCAL) {
> >                 rt = ip_route_output_ports(dev_net(skb->dev), &fl4,
> NULL,
> >                                            eiph->daddr, eiph->saddr,
> 0, 0,
> >                                            IPPROTO_IPIP,
> > --
> > 2.9.4
> >
> Have you already seen any problem triggered by this?
> 
> I don't really think there's a use-after-free issue here.
> 
> The entire IPv6 input path, including ICMPv6 error handling, runs under
> rcu_read_lock(). Since dst_release() uses call_rcu_hurry() to defer the actual
> freeing until after the RCU grace period, accessing rt->rt_flags after
> ip_rt_put(rt) is completely safe in this context.
> 

Thanks for the review. However, The issue is that after ip_rt_put(rt), the rt pointer may be logically freed if refcnt drops to zero, and dereferencing it afterwards is illegal even if memory isn't recycled immediately. The patch avoids this by caching rt->rt_flags before the put operation. This is a defensive fix against potential UAF reported by static analyzers. 

Thanks

[Li,Rongqing] 


> Thanks.

^ permalink raw reply

* [PATCH net] net: mana: Return error code from mana_create_rxq()
From: Aditya Garg @ 2026-07-18  2:48 UTC (permalink / raw)
  To: kys, haiyangz, wei.liu, decui, longli, andrew+netdev, davem,
	edumazet, kuba, pabeni, ernis, horms, dipayanroy, gargaditya,
	shacharr, stephen, linux-hyperv, netdev, linux-kernel, ssengar,
	gargaditya

mana_create_rxq() returns a struct mana_rxq pointer and returns NULL on
any failure. The caller, mana_add_rx_queues(), cannot tell what went
wrong and hardcodes the error as -ENOMEM. As a result the actual failure
reported by the lower layers (for example -EPROTO from a failed HW
request) is masked and every RX queue creation failure looks like an
out-of-memory error.

Change mana_create_rxq() to return an int and pass the created rxq back
to the caller through an output parameter. The caller now propagates the
returned error code directly instead of substituting -ENOMEM.

Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)")
Signed-off-by: Aditya Garg <gargaditya@linux.microsoft.com>
Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
---
 drivers/net/ethernet/microsoft/mana/mana_en.c | 21 ++++++++++---------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 9d9bfd116dab..9162e356e0c1 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -2811,9 +2811,9 @@ static int mana_create_page_pool(struct mana_rxq *rxq, struct gdma_context *gc)
 	return 0;
 }
 
-static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc,
-					u32 rxq_idx, struct mana_eq *eq,
-					struct net_device *ndev)
+static int mana_create_rxq(struct mana_port_context *apc, u32 rxq_idx,
+			   struct mana_eq *eq, struct net_device *ndev,
+			   struct mana_rxq **rxq_out)
 {
 	struct gdma_dev *gd = apc->ac->gdma_dev;
 	struct mana_obj_spec wq_spec;
@@ -2829,7 +2829,7 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc,
 
 	rxq = kvzalloc_flex(*rxq, rx_oobs, apc->rx_queue_size);
 	if (!rxq)
-		return NULL;
+		return -ENOMEM;
 
 	rxq->ndev = ndev;
 	rxq->num_rx_buf = apc->rx_queue_size;
@@ -2923,14 +2923,16 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc,
 
 	mana_gd_ring_cq(cq->gdma_cq, SET_ARM_BIT);
 out:
-	if (!err)
-		return rxq;
+	if (!err) {
+		*rxq_out = rxq;
+		return 0;
+	}
 
 	netdev_err(ndev, "Failed to create RXQ: err = %d\n", err);
 
 	mana_destroy_rxq(apc, rxq, false);
 
-	return NULL;
+	return err;
 }
 
 static void mana_create_rxq_debugfs(struct mana_port_context *apc, int idx)
@@ -2963,9 +2965,8 @@ static int mana_add_rx_queues(struct mana_port_context *apc,
 	int i;
 
 	for (i = 0; i < apc->num_queues; i++) {
-		rxq = mana_create_rxq(apc, i, &apc->eqs[i], ndev);
-		if (!rxq) {
-			err = -ENOMEM;
+		err = mana_create_rxq(apc, i, &apc->eqs[i], ndev, &rxq);
+		if (err) {
 			netdev_err(ndev, "Failed to create rxq %d : %d\n", i, err);
 			goto out;
 		}
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v4 2/5] drm/ras: Introduce error threshold
From: Raag Jadav @ 2026-07-18  5:26 UTC (permalink / raw)
  To: Tauro, Riana
  Cc: intel-xe, dri-devel, netdev, simona.vetter, airlied, kuba,
	lijo.lazar, Hawking.Zhang, davem, pabeni, edumazet, dev,
	zachary.mckevitt, rodrigo.vivi, michal.wajdeczko, matthew.d.roper,
	mallesh.koujalagi
In-Reply-To: <0769389d-e73a-4dda-878c-2bc9e59deff3@intel.com>

On Fri, Jul 03, 2026 at 10:43:06AM +0530, Tauro, Riana wrote:
> On 23-06-2026 15:39, Raag Jadav wrote:
> > Add get-error-threshold and set-error-threshold command support which
> > allows querying/setting error threshold of the counter. Threshold in RAS
> > context means the number of errors the hardware is expected to accumulate
> > before it raises them to software. This is to have a fine grained control
> > over error notifications that are raised by the hardware.

...

> >    *     + The error counters in the driver doesn't need to be contiguous, but the
> >    *       driver must return -ENOENT to the query_error_counter as an indication
> >    *       that the ID should be skipped and not listed in the netlink API.
> > + *     + The driver can optionally implement query_error_threshold() and
> > + *       set_error_threshold() callbacks to facilitate getting/setting error
> > + *       threshold of the counter. Threshold in RAS context means the number of
> > + *       errors the hardware is expected to accumulate before it raises them to
> > + *       software. This is to have a fine grained control over error notifications
> > + *       that are raised by the hardware.
> > + *     + The driver is responsible for error threshold bounds checking.
> > + *     + Threshold of 0 can mean invalid threshold or act as a disable notifications
> > + *       toggle for that counter depending on usecase and the driver is responsible
> > + *       for handling it as needed.
> 
> I know i asked you to add this in last rev. But after reading this,
> error-threshold bounds checking at driver level
> should be sufficient.  It's upto the driver on what behavior needs to be
> implemented.
> 
> Some may notify on reaching threshold or crossing threshold.
> I think we should drop this sentence here. Let me know your thoughts.
> sorry for the confusion.

I think it adds more context and good to have in case someone comes up
looking for the details.

...

> > +	/**
> > +	 * @query_error_threshold:
> > +	 *
> > +	 * This callback is used by drm-ras to query error threshold of a
> > +	 * specific counter.
> > +	 *
> > +	 * Driver should expect query_error_threshold() to be called with
> > +	 * error_id from `error_counter_range.first` to
> > +	 * `error_counter_range.last`.
> > +	 *
> > +	 * Returns: 0 on success, negative error code on failure.
> > +	 */
> > +	int (*query_error_threshold)(struct drm_ras_node *node, u32 error_id, const char **name,
> > +				     u32 *threshold);
> 
> Add a blank line

Sure.

> With these fixed
> 
> Reviewed-by: Riana Tauro <riana.tauro@intel.com>

Awesome.

Raag

^ permalink raw reply

* Re: [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
From: Luiz Angelo Daros de Luca @ 2026-07-18  5:40 UTC (permalink / raw)
  To: Johan Alvarado
  Cc: Mieczyslaw Nalewaj, linusw, alsi, andrew, olteanv, kuba, davem,
	edumazet, pabeni, linux, maxime.chevallier, kuncy7, netdev,
	linux-kernel
In-Reply-To: <db8f9fb11e7e250eab9c98eafab1ba3e@c127.dev>

If the cold-soak issue is indeed device-related, it might be that the
driver is loading too early in the boot process. Stanislaw reported
that reloading the module makes everything work as expected.

I don't see why the second reset would behave better than the first
one. In fact, if the reset pin is connected and configured, the driver
will reset the ASIC twice during the probe. Stanislaw, do you have a
reset pin or a reset controller configured? That might be related.

The driver currently performs a full software ASIC reset via bit(0),
but there are other reset bits available that you could try:

##### **Family B, C, D:**

```
15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|                       RESERVED    |GPH|NIC|805|SDS|CFG|SW |CHP|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
```
 * **GPH (6:6)**: `GPHY_RESET`. Gigphy (Copper PHYs) Reset.
 * **NIC (5:5)**: `NIC_RST`. Network Interface Controller Reset.
 * **805 (4:4)**: `DW8051_RST`. Internal DW8051 MCU Reset.
 * **SDS (3:3)**: `SDS_RST`. SerDes Reset.
 * **CFG (2:2)**: `CONFIG_RST`. Configuration register soft reset.
 * **SW (1:1)**: `SW_RST`. Software Reset (`0x0002`). Clears soft state.
 * **CHP (0:0)**: `CHIP_RST`. Chip (ASIC) full reset (`0x0001`).

I would expect the CHIP_RST (bit 0) to clear everything, but you never
know... Also, keeping DW8051_RST asserted indefinitely might be
overkill.

This series adds a required feature for several devices. Even if it
introduces an edge-case issue for a few of them, lacking this feature
entirely is worse. If a patch is proposed to fix the cold-soak boot
issue, we can review it here as usual.

I would feel more comfortable if I could test this code myself, but my
device only uses RGMII. Anyway, everything looks good. Thanks, Johan.

Reviewed-by:  Luiz Angelo Daros de Luca <luizluca@gmail.com>

^ permalink raw reply

* Re: [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
From: Luiz Angelo Daros de Luca @ 2026-07-18  5:46 UTC (permalink / raw)
  To: Johan Alvarado
  Cc: Mieczyslaw Nalewaj, linusw, alsi, andrew, olteanv, kuba, davem,
	edumazet, pabeni, linux, maxime.chevallier, kuncy7, netdev,
	linux-kernel
In-Reply-To: <03309c370652c5ca36cf9972756efa5c@c127.dev>

The only model that could use (H)SGMII in a different interface is
RTL8370MB. However, that ASIC and RTL8310SR might need even more
changes in the driver. For now, the code looks good.

Reviewed-by:  Luiz Angelo Daros de Luca <luizluca@gmail.com>

^ permalink raw reply

* [PATCH net-next] net: dsa: realtek: rtl8365mb: use devm_mutex_init
From: Luiz Angelo Daros de Luca @ 2026-07-18  5:59 UTC (permalink / raw)
  To: Linus Walleij, Alvin Šipraga, Andrew Lunn, Vladimir Oltean,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, Luiz Angelo Daros de Luca

mutex_destroy() is needed while debugging mutex. Instead of calling it
directly on driver .remove(), just use devm.

Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
---
 drivers/net/dsa/realtek/rtl8365mb_main.c | 15 ++++++++++++---
 drivers/net/dsa/realtek/rtl83xx.c        | 14 +++++++++++---
 2 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 5ac091bf93c9..c787fdb75413 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -1988,16 +1988,19 @@ static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port,
 	spin_unlock(&p->stats_lock);
 }
 
-static void rtl8365mb_stats_setup(struct realtek_priv *priv)
+static int rtl8365mb_stats_setup(struct realtek_priv *priv)
 {
 	struct rtl8365mb *mb = priv->chip_data;
 	struct dsa_switch *ds = &priv->ds;
 	struct dsa_port *dp;
+	int ret;
 
 	/* Per-chip global mutex to protect MIB counter access, since doing
 	 * so requires accessing a series of registers in a particular order.
 	 */
-	mutex_init(&mb->mib_lock);
+	ret = devm_mutex_init(priv->dev, &mb->mib_lock);
+	if (ret)
+		return ret;
 
 	dsa_switch_for_each_available_port(dp, ds) {
 		struct rtl8365mb_port *p = &mb->ports[dp->index];
@@ -2010,6 +2013,8 @@ static void rtl8365mb_stats_setup(struct realtek_priv *priv)
 		 */
 		INIT_DELAYED_WORK(&p->mib_work, rtl8365mb_stats_poll);
 	}
+
+	return 0;
 }
 
 static void rtl8365mb_stats_teardown(struct realtek_priv *priv)
@@ -2567,7 +2572,11 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
 	}
 
 	/* Start statistics counter polling */
-	rtl8365mb_stats_setup(priv);
+	ret = rtl8365mb_stats_setup(priv);
+	if (ret) {
+		dev_err(priv->dev, "failed to setup stats\n");
+		goto out_teardown_irq;
+	}
 
 	return 0;
 
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index 90843d52c5a8..35df809a5951 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -156,9 +156,17 @@ rtl83xx_probe(struct device *dev,
 	if (!priv)
 		return ERR_PTR(-ENOMEM);
 
-	mutex_init(&priv->map_lock);
-	mutex_init(&priv->vlan_lock);
-	mutex_init(&priv->l2_lock);
+	ret = devm_mutex_init(dev, &priv->map_lock);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = devm_mutex_init(dev, &priv->vlan_lock);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = devm_mutex_init(dev, &priv->l2_lock);
+	if (ret)
+		return ERR_PTR(ret);
 
 	rc.lock_arg = priv;
 	priv->map = devm_regmap_init(dev, NULL, priv, &rc);

---
base-commit: ce6b4d3216b63f902bb8e9695ee6c10c83415f65
change-id: 20260718-realtek_mutext-41a072e5cd24

Best regards,
--  
Luiz Angelo Daros de Luca <luizluca@gmail.com>


^ permalink raw reply related

* [PATCH iproute2-next v3] ipmaddr: use RTM_GETMULTICAST to list multicast addresses
From: Yuyang Huang @ 2026-07-18  6:48 UTC (permalink / raw)
  To: Yuyang Huang; +Cc: David Ahern, netdev

Replace /proc/net/igmp and /proc/net/igmp6 parsing in "ip maddr show"
with RTM_GETMULTICAST dumps. The kernel dumps IPv6 multicast addresses
via netlink since the beginning, IPv4 since v6.15 (eb4e17a1d915), and
reports the group users count via IFA_MC_USERS since kernel commits
7cb8198761e6 and e1d0f3f08391.

The netlink result is only used when it carries the same information
as procfs: if the dump fails (e.g. no IPv4 dump support) or any entry
lacks IFA_MC_USERS, the result is discarded and the procfs parsers
run as before, so output is unchanged on older kernels.

Link-layer multicast addresses are still read from
/proc/net/dev_mcast as there is no netlink API for them.

When a device is given, its ifindex is passed in the dump request so
strict-check kernels filter the dump server side; received entries
are checked against the ifindex again for kernels that ignore the
request field. An unknown device keeps printing an empty list.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
Changes in v2:
- accept_maddr(): only accept RTM_NEWMULTICAST; the kernel never sends
  RTM_GETMULTICAST in dump replies

Changes in v3:
- accept_maddr(): match RTM_GETMULTICAST again, reverting the v2
  change; dump replies do carry RTM_GETMULTICAST (RTM_NEWMULTICAST is
  only used for notifications), confirmed by testing on a live kernel

 include/libnetlink.h |   3 ++
 ip/ipmaddr.c         | 109 +++++++++++++++++++++++++++++++++++++++++--
 lib/libnetlink.c     |  26 +++++++++++
 3 files changed, 134 insertions(+), 4 deletions(-)

diff --git a/include/libnetlink.h b/include/libnetlink.h
index e91505d9..518b8714 100644
--- a/include/libnetlink.h
+++ b/include/libnetlink.h
@@ -62,6 +62,9 @@ typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen);
 int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
 		      req_filter_fn_t filter_fn)
 	__attribute__((warn_unused_result));
+int rtnl_mcaddrdump_req(struct rtnl_handle *rth, int family,
+			req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
 int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family)
 	__attribute__((warn_unused_result));
 int rtnl_routedump_req(struct rtnl_handle *rth, int family,
diff --git a/ip/ipmaddr.c b/ip/ipmaddr.c
index 462b409e..95b703d2 100644
--- a/ip/ipmaddr.c
+++ b/ip/ipmaddr.c
@@ -27,6 +27,7 @@
 
 static struct {
 	char *dev;
+	int  index;
 	int  family;
 } filter;
 
@@ -207,6 +208,93 @@ static void read_igmp6(struct ma_info **result_p)
 	fclose(fp);
 }
 
+struct maddr_dump_ctx {
+	struct ma_info *list;
+	bool mc_users_missing;
+};
+
+static int maddr_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+
+	ifm->ifa_index = filter.index;
+
+	return 0;
+}
+
+static int accept_maddr(struct nlmsghdr *n, void *arg)
+{
+	struct maddr_dump_ctx *ctx = arg;
+	struct ifaddrmsg *ifm = NLMSG_DATA(n);
+	int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifm));
+	struct rtattr *tb[IFA_MAX + 1];
+	struct ma_info *ma;
+
+	if (n->nlmsg_type != RTM_GETMULTICAST)
+		return 0;
+
+	if (len < 0)
+		return -1;
+
+	if (filter.index && filter.index != ifm->ifa_index)
+		return 0;
+
+	parse_rtattr(tb, IFA_MAX, IFA_RTA(ifm), len);
+
+	if (!tb[IFA_MULTICAST] ||
+	    RTA_PAYLOAD(tb[IFA_MULTICAST]) > sizeof(ma->addr.data))
+		return 0;
+
+	if (!tb[IFA_MC_USERS]) {
+		ctx->mc_users_missing = true;
+		return 0;
+	}
+
+	ma = calloc(1, sizeof(*ma));
+	if (ma == NULL)
+		return -1;
+
+	ma->index = ifm->ifa_index;
+	strlcpy(ma->name, ll_index_to_name(ifm->ifa_index), sizeof(ma->name));
+	ma->addr.family = ifm->ifa_family;
+	ma->addr.bytelen = RTA_PAYLOAD(tb[IFA_MULTICAST]);
+	ma->addr.bitlen = ma->addr.bytelen << 3;
+	memcpy(ma->addr.data, RTA_DATA(tb[IFA_MULTICAST]), ma->addr.bytelen);
+	ma->users = rta_getattr_u32(tb[IFA_MC_USERS]);
+	maddr_ins(&ctx->list, ma);
+
+	return 0;
+}
+
+static int read_maddr_netlink(int family, struct ma_info **result_p)
+{
+	struct maddr_dump_ctx ctx = {};
+	struct ma_info *ma;
+	int err;
+
+	rth.flags |= RTNL_HANDLE_F_SUPPRESS_NLERR;
+	err = rtnl_mcaddrdump_req(&rth, family,
+				  filter.index ? maddr_dump_filter : NULL);
+	if (err >= 0)
+		err = rtnl_dump_filter(&rth, accept_maddr, &ctx);
+	rth.flags &= ~RTNL_HANDLE_F_SUPPRESS_NLERR;
+
+	/* Kernels that dump multicast addresses but do not report the
+	 * users count via IFA_MC_USERS cannot replace procfs.
+	 */
+	if (err < 0 || ctx.mc_users_missing) {
+		maddr_clear(ctx.list);
+		return -1;
+	}
+
+	while ((ma = ctx.list) != NULL) {
+		ctx.list = ma->next;
+		maddr_ins(result_p, ma);
+	}
+
+	return 0;
+}
+
 static void print_maddr(FILE *fp, struct ma_info *list)
 {
 	print_string(PRINT_FP, NULL, "\t", NULL);
@@ -291,12 +379,25 @@ static int multiaddr_list(int argc, char **argv)
 		argv++; argc--;
 	}
 
+	if (filter.dev) {
+		filter.index = ll_name_to_index(filter.dev);
+		/* an unknown device has no multicast addresses */
+		if (!filter.index) {
+			print_mlist(stdout, NULL);
+			return 0;
+		}
+	}
+
 	if (!filter.family || filter.family == AF_PACKET)
 		read_dev_mcast(&list);
-	if (!filter.family || filter.family == AF_INET)
-		read_igmp(&list);
-	if (!filter.family || filter.family == AF_INET6)
-		read_igmp6(&list);
+	if (!filter.family || filter.family == AF_INET) {
+		if (read_maddr_netlink(AF_INET, &list) < 0)
+			read_igmp(&list);
+	}
+	if (!filter.family || filter.family == AF_INET6) {
+		if (read_maddr_netlink(AF_INET6, &list) < 0)
+			read_igmp6(&list);
+	}
 	print_mlist(stdout, list);
 	maddr_clear(list);
 	return 0;
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index 8905e297..edf3a8ba 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -336,6 +336,32 @@ int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
 	return send(rth->fd, &req, sizeof(req), 0);
 }
 
+int rtnl_mcaddrdump_req(struct rtnl_handle *rth, int family,
+			req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ifaddrmsg ifm;
+		char buf[128];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
+		.nlh.nlmsg_type = RTM_GETMULTICAST,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ifm.ifa_family = family,
+	};
+
+	if (filter_fn) {
+		int err;
+
+		err = filter_fn(&req.nlh, sizeof(req));
+		if (err)
+			return err;
+	}
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
 int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family)
 {
 	struct {
-- 
2.43.0


^ permalink raw reply related

* [PATCH net 1/1] xfrm: drop ESP-in-TCP packets with no ingress device
From: Ren Wei @ 2026-07-18  7:12 UTC (permalink / raw)
  To: netdev
  Cc: steffen.klassert, herbert, davem, edumazet, pabeni, horms, sd,
	vega, roxy520tt, enjou1224z
In-Reply-To: <cover.1784312563.git.roxy520tt@gmail.com>

From: Zhiling Zou <roxy520tt@gmail.com>

ESP-in-TCP receives records through the TCP strparser. handle_esp()
restores skb->dev from the saved skb_iif before passing the packet into
the XFRM input path.

Queued TCP data can be processed after the original ingress device has
been removed, for example during veth or net namespace teardown. In that
case dev_get_by_index_rcu() returns NULL. The XFRM IPv4 and IPv6 input
paths both expect skb->dev to be valid while building the route lookup,
so queued ESP-in-TCP data can dereference a NULL device.

Drop the packet if the saved ingress device can no longer be resolved.
Such a packet can no longer be routed through the normal XFRM receive
path, and this preserves the existing behaviour for packets whose ingress
device still exists.

Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <roxy520tt@gmail.com>
Assisted-by: Codex:gpt-5.4
Reviewed-by: Ren Wei <enjou1224z@gmail.com>
---
 net/xfrm/espintcp.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c
index 374e1b964438..fc01ce89492a 100644
--- a/net/xfrm/espintcp.c
+++ b/net/xfrm/espintcp.c
@@ -37,6 +37,11 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk)
 
 	rcu_read_lock();
 	skb->dev = dev_get_by_index_rcu(sock_net(sk), skb->skb_iif);
+	if (!skb->dev) {
+		XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR);
+		kfree_skb(skb);
+		goto out;
+	}
 	local_bh_disable();
 #if IS_ENABLED(CONFIG_IPV6)
 	if (sk->sk_family == AF_INET6)
@@ -45,6 +50,7 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk)
 #endif
 		xfrm4_rcv_encap(skb, IPPROTO_ESP, 0, TCP_ENCAP_ESPINTCP);
 	local_bh_enable();
+out:
 	rcu_read_unlock();
 }
 
-- 
2.43.0

^ permalink raw reply related

* Re: [REGRESSION][BISECTED] stmmac: suspend hangs since 1b9707e6f1a9 ("net: stmmac: enable RPS and RBU interrupts")
From: tresonic @ 2026-07-18  7:35 UTC (permalink / raw)
  To: Andrew Lunn; +Cc: netdev, regressions, rmk+kernel, kuba, Maxime Chevallier
In-Reply-To: <09ee8651-4f29-4ae0-8e82-a32a1f7ad04a@lunn.ch>

Thank you for you explanations!

> The interrupt is being enabled in the init_chan call in
> stmmac_dma_ops. Ideally, it should be disabled in a mirror function,
> which currently does not exist. So maybe deinit_chan() needs
> adding. But where to call it from? init_chan() is called from
> stmmac_init_dma_engine(), from stmmac_hw_setup(). stmmac_resume() does
> call this. So we need something in stmmac_suspend(). Maybe in
> stmmac_stop_all_dma()?
> 
> stmmac is messy, there are often not mirror functions. If there is a
> stmmac_init_dma_engine() there should be
> stmmac_deinit_dma_engine(). If there is stmmac_hw_setup() there should
> be stmmac_hw_tairdown(). But none of these seem to exist. 
> 
> Anyway, do you want to try to implement deinit_chan() and call it from
> stmmac_stop_all_dma()?
Yes I'd really like to implement a solution here.
This is my try, but I still have some questions:
- is it ok to disable all interrupts on deinit_chan()?
- maybe the interrupt could also just be disabled in stop_rx?

diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
index 829a23bdad01..65c243fb829f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
@@ -125,6 +125,17 @@ static void dwmac410_dma_init_channel(struct stmmac_priv *priv,
 	       ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
 }
 
+static void dwmac410_dma_deinit_channel(struct stmmac_priv *priv,
+					void __iomem *ioaddr, u32 chan)
+{
+	const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+	u32 value;
+
+	value = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+	value &= ~DMA_CHAN_INTR_DEFAULT_MASK_4_10;
+	writel(value, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+}
+
 static void dwmac4_dma_init(void __iomem *ioaddr,
 			    struct stmmac_dma_cfg *dma_cfg)
 {
@@ -577,6 +588,7 @@ const struct stmmac_dma_ops dwmac410_dma_ops = {
 	.reset = dwmac4_dma_reset,
 	.init = dwmac4_dma_init,
 	.init_chan = dwmac410_dma_init_channel,
+	.deinit_chan = dwmac410_dma_deinit_channel,
 	.init_rx_chan = dwmac4_dma_init_rx_chan,
 	.init_tx_chan = dwmac4_dma_init_tx_chan,
 	.axi = dwmac4_dma_axi,
diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h
index e6317b94fff7..04dafec021b4 100644
--- a/drivers/net/ethernet/stmicro/stmmac/hwif.h
+++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h
@@ -170,6 +170,8 @@ struct stmmac_dma_ops {
 	void (*init)(void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg);
 	void (*init_chan)(struct stmmac_priv *priv, void __iomem *ioaddr,
 			  struct stmmac_dma_cfg *dma_cfg, u32 chan);
+	void (*deinit_chan)(struct stmmac_priv *priv, void __iomem *ioaddr,
+			    u32 chan);
 	void (*init_rx_chan)(struct stmmac_priv *priv, void __iomem *ioaddr,
 			     struct stmmac_dma_cfg *dma_cfg,
 			     dma_addr_t phy, u32 chan);
@@ -235,6 +237,8 @@ struct stmmac_dma_ops {
 	stmmac_do_void_callback(__priv, dma, init, __args)
 #define stmmac_init_chan(__priv, __args...) \
 	stmmac_do_void_callback(__priv, dma, init_chan, __priv, __args)
+#define stmmac_deinit_chan(__priv, __args...) \
+	stmmac_do_void_callback(__priv, dma, deinit_chan, __priv, __args)
 #define stmmac_init_rx_chan(__priv, __args...) \
 	stmmac_do_void_callback(__priv, dma, init_rx_chan, __priv, __args)
 #define stmmac_init_tx_chan(__priv, __args...) \
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 2a0d7eff88d3..8504ecc3dbeb 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -2560,13 +2560,16 @@ static void stmmac_stop_all_dma(struct stmmac_priv *priv)
 {
 	u8 rx_channels_count = priv->plat->rx_queues_to_use;
 	u8 tx_channels_count = priv->plat->tx_queues_to_use;
+	u8 max_chan = max(rx_channels_count, tx_channels_count);
 	u8 chan;
 
-	for (chan = 0; chan < rx_channels_count; chan++)
-		stmmac_stop_rx_dma(priv, chan);
-
-	for (chan = 0; chan < tx_channels_count; chan++)
-		stmmac_stop_tx_dma(priv, chan);
+	for (chan = 0; chan < max_chan; chan++) {
+		if (chan < rx_channels_count)
+			stmmac_stop_rx_dma(priv, chan);
+		if (chan < tx_channels_count)
+			stmmac_stop_tx_dma(priv, chan);
+		stmmac_deinit_chan(priv, priv->ioaddr, chan);
+	}
 }
 
 /**


tresonic


^ permalink raw reply related

* Re: [PATCH net v3] net: dpaa: fix mode setting
From: Christian Zigotzky @ 2026-07-18  7:35 UTC (permalink / raw)
  To: Sean Anderson
  Cc: Michael Walle, Madalin Bucur, Andrew Lunn, David S Miller,
	Eric Dumazet, Jakub Kicinski, Abeni Paolo, Christian Zigotzky,
	netdev, linux-kernel, linuxppc-dev, Trevor Dickinson,
	mad skateman, hypexed
In-Reply-To: <4f7497cf-83ed-47cd-2e7b-d06ebe319b61@linux.dev>

On  17 July 2026 at 11:11 am, Sean Anderson <sean.anderson@linux.dev> wrote:

On 7/17/26 09:20, Michael Walle wrote:

---
I didn't grab Sean's Rb tag as this is somewhat different.
Changes in v3:
 - keep the mode setting also in .adjust_link().
 - reword the commit message, to be (hopefully) more precise
 - Link to v2: https://lore.kernel.org/r/20260710143430.2276141-1-mwalle@kernel.org/
Changes in v2:
 - the setting is/was based on the maximum speed, not the current
   speed. thus, move the setting into mac_config().
 - Link to v1: https://lore.kernel.org/r/20260706121011.1948906-1-mwalle@kernel.org/
 .../net/ethernet/freescale/fman/fman_dtsec.c    | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/freescale/fman/fman_dtsec.c b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
index fe35703c509e..b8d70c0ecb6c 100644
--- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c
+++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c
@@ -900,22 +900,28 @@ static void dtsec_mac_config(struct phylink_config *config, unsigned int mode,
 {
     struct mac_device *mac_dev = fman_config_to_mac(config);
     struct dtsec_regs __iomem *regs = mac_dev->fman_mac->regs;
-    u32 tmp;
+    u32 ecntrl, maccfg2;
+
+    maccfg2 = ioread32be(&regs->maccfg2);
+    maccfg2 &= ~(MACCFG2_NIBBLE_MODE | MACCFG2_BYTE_MODE);
       switch (state->interface) {
     case PHY_INTERFACE_MODE_RMII:
-        tmp = DTSEC_ECNTRL_RMM;
+        ecntrl = DTSEC_ECNTRL_RMM;
+        maccfg2 |= MACCFG2_NIBBLE_MODE;
         break;
     case PHY_INTERFACE_MODE_RGMII:
     case PHY_INTERFACE_MODE_RGMII_ID:
     case PHY_INTERFACE_MODE_RGMII_RXID:
     case PHY_INTERFACE_MODE_RGMII_TXID:
-        tmp = DTSEC_ECNTRL_GMIIM | DTSEC_ECNTRL_RPM;
+        ecntrl = DTSEC_ECNTRL_GMIIM | DTSEC_ECNTRL_RPM;
+        maccfg2 |= MACCFG2_BYTE_MODE;
         break;
     case PHY_INTERFACE_MODE_SGMII:
     case PHY_INTERFACE_MODE_1000BASEX:
     case PHY_INTERFACE_MODE_2500BASEX:
-        tmp = DTSEC_ECNTRL_TBIM | DTSEC_ECNTRL_SGMIIM;
+        ecntrl = DTSEC_ECNTRL_TBIM | DTSEC_ECNTRL_SGMIIM;
+        maccfg2 |= MACCFG2_BYTE_MODE;
         break;
     default:
         dev_warn(mac_dev->dev, "cannot configure dTSEC for %s\n",
@@ -923,7 +929,8 @@ static void dtsec_mac_config(struct phylink_config *config, unsigned int mode,
         return;
     }
 -    iowrite32be(tmp, &regs->ecntrl);
+    iowrite32be(ecntrl, &regs->ecntrl);
+    iowrite32be(maccfg2, &regs->maccfg2);
 }
   static void dtsec_link_up(struct phylink_config *config, struct phy_device *phy,

Reviewed-by: Sean Anderson <sean.anderson@linux.dev>

Christian, can you test this patch with ethernet at 100/1G speed if you still have
access to those P5020/P5040 boards?

- - -

Yes, I will test it as soon as possible.

- Christian

^ permalink raw reply related

* [PATCH net] net: slip: serialize receive against buffer reallocation
From: Sungmin Kang @ 2026-07-18  7:36 UTC (permalink / raw)
  To: netdev; +Cc: andrew+netdev, davem, edumazet, kuba, pabeni

sl_realloc_bufs() replaces rbuff and updates buffsize while holding
sl->lock. slip_receive_buf() reads those fields and writes through rbuff
without holding the lock.

An MTU change can therefore race with receive processing. An MTU shrink
can expose the new smaller rbuff with the old larger bound, causing an
out-of-bounds write. A receive callback which already loaded the old
rbuff can instead continue writing after that buffer has been freed.

Serialize receive processing with sl_realloc_bufs() by holding sl->lock
while consuming each receive batch.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Sungmin Kang <726ksm@gmail.com>
---
Testing:

Before this change, a generic-KASAN guest reported both a
slab-out-of-bounds write and a slab-use-after-free in slip_receive_buf()
while receive data was raced with MTU changes. A non-KASAN build with
SLUB redzone and poisoning also detected cross-object corruption.

With this change, the same KASAN stress test ran for 180 seconds and
completed 38,137,741 receive-writer iterations and 216,165 MTU changes
without a KASAN report, warning, or panic.

 drivers/net/slip/slip.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/slip/slip.c b/drivers/net/slip/slip.c
index 820e1a8fc..faae711cf 100644
--- a/drivers/net/slip/slip.c
+++ b/drivers/net/slip/slip.c
@@ -693,6 +693,8 @@ static void slip_receive_buf(struct tty_struct *tty, const u8 *cp, const u8 *fp,
 	if (!sl || sl->magic != SLIP_MAGIC || !netif_running(sl->dev))
 		return;
 
+	spin_lock_bh(&sl->lock);
+
 	/* Read the characters out of the buffer */
 	while (count--) {
 		if (fp && *fp++) {
@@ -708,6 +710,8 @@ static void slip_receive_buf(struct tty_struct *tty, const u8 *cp, const u8 *fp,
 #endif
 			slip_unesc(sl, *cp++);
 	}
+
+	spin_unlock_bh(&sl->lock);
 }
 
 /************************************
-- 
2.49.0.windows.1

^ permalink raw reply related

* Re: [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
From: Stanislaw @ 2026-07-18  7:55 UTC (permalink / raw)
  To: Johan Alvarado, Mieczyslaw Nalewaj
  Cc: Linus Walleij, Alvin Sipraga, Andrew Lunn, Vladimir Oltean,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Russell King, Maxime Chevallier, Luiz Angelo Daros de Luca,
	netdev, linux-kernel
In-Reply-To: <CAJq09z6bqVdtmuf0LVe_WjVYM9GmjaNiqjnOALLut4jCoZBLRQ@mail.gmail.com>

Hi Luiz, Mieczyslaw,

> Stanislaw, do you have a reset pin or a reset controller configured?

Yes - a reset GPIO, no reset controller:

  reset-gpios = <&tlmm 33 GPIO_ACTIVE_LOW>;

on the switch node. So on this board the probe path does run both
resets: the GPIO hard reset from rtl83xx_probe() plus the soft reset
during setup. That is worth stressing, because the re-probe that cures
the cold-soak state performs *exactly the same sequence* - same GPIO
reset, same soft reset, same full init - as the probe that lands in
the bad state. The only difference is that it is the second run. So
it does not look like a missing reset; more like something about the
very first init pass on cold silicon.

On "loading too early": the driver is a module here and probes ~20 s
after power-on (rootfs mount), so the rails have long been up by then.
That said, 20 s of a cold board is a different thermal/electrical
state than the same board a minute after a reboot, and short
power-cycles (~1 min) never reproduce the issue - only a multi-hour
soak does.

Your reset-bit map is exactly the experiment this bench needs. Next
cold-bad morning I will try the individual reset bits (SDS_RST, SW_RST,
CHIP_RST, DW8051_RST...) on the degraded state, before falling back to
the full re-probe, to find the minimal action that cures it. I'll
report what I find.

> Please repeat the test only with the fix "the ~98 ms PLL-settle delay
> plus the 0x060C-0x060F Local Jam Table analog calibration".

Done - last night's cold soak ran an image with only those two changes
on top of the v6 series (busy-wait reverted). Result: the bad state
came up as usual, no improvement - consistent with Johan's analysis of
where those writes actually land.

This time I had ethtool on the box before curing it, so here is the
per-port MIB of the bad state (~35 min after the cold boot, switch
reporting 2.5G/Full link up on the SerDes, wan 1G/Full to the wire):

  CPU -> switch direction (broken):
    eth0 (SoC gmac1):        995 frames / 282,945 octets tx, no errors
    switch port 6 (SerDes):  dot3StatsFCSErrors: 506
                             etherStatsDropEvents: 506
    wan wire egress:         zero - no ifOut counters at all, although
                             the kernel handed ~990 frames for tx

  switch -> CPU direction (clean):
    p06_ifOutOctets: 172,231 == eth0 rx octet count: 172,231
    byte-exact, zero errors - that's why ingress from upstream works

So the cold-soak bad state is now precisely characterized: over half of
the frames arriving from the SoC FCS-fail at the switch SerDes ingress
and are dropped (and nothing reaches the wan wire), while the switch's
transmit side is byte-exact clean. Since a full re-probe of the switch
alone cures it - the SoC side is never touched - the bad half is the
switch's SerDes receiver, left in that state by the first cold init.

Next cold morning I'll do the reset-bit isolation on the degraded
state, per Luiz's map, before falling back to the full re-probe.

Best regards,
Stanislaw

^ permalink raw reply

* [PATCH net v7 0/3] tipc: fix NULL deref in tipc_named_node_up() on empty publication list
From: Weiming Shi @ 2026-07-18  9:09 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion; +Cc: Xiang Mei, Weiming Shi

This series continues the fix for the NULL dereference in
tipc_named_node_up() on an empty publication list.

Patch 1/3 carries Tung Nguyen's defer-to-workqueue approach, suggested
by Jon Maloy on the thread as the replacement for the item-less bulk
from v2. Tung's RFC only exists as an inline diff in the thread, so I
folded it into this series, keeping his Signed-off-by. I tested it in
our two-node QEMU setup (veth pair, UDP bearers, node-id addressing),
both with an unprivileged user namespace and as root: the unpatched
kernel panics on the first run of the same reproducer, the patched one
distributes normally with a non-empty list.

While testing we found two residual issues in the approach, fixed by
patches 2/3 and 3/3.

Patch 2/3: tipc_net_finalize() does not check the return value of
tipc_nametbl_publish(). If the publish fails, for example on a
GFP_ATOMIC allocation failure, the node is finalized but cluster_scope
stays empty. The deferred worker then calls named_distribute() with
an empty list and hits the same NULL dereference, this time on the
workqueue. With this patch the worker re-checks the list and skips
cleanly, no crash and no link flap. The tail stamp in
named_distribute() also gets an empty-queue guard.

Patch 3/3: a repeated NODE_UP while the bulk work is pending takes a
node reference that is never dropped, because schedule_work() returns
false when the work is already queued. Found by flapping the bearer
during the defer window. One reference is leaked per repeated
NODE_UP.

Changes in v7:
 - Patch 1/3: add the missing kernel-doc description for the new work
   member of struct tipc_node, fixing the W=1 build warning reported
   by the kernel test robot. No code change.

Changes in v6:
 - Make the series self-contained: fold Tung Nguyen's base patch into
   the series (1/3), keeping his Signed-off-by. The version sent as
   v5 only carried the two follow-ups and depended on his patch from
   the thread; the code changes in 2/3 and 3/3 are unchanged from
   that version.

Changes in v5:
 - Replace the item-less bulk approach with Tung Nguyen's
   defer-to-workqueue RFC, which fixes the reported bug in our
   testing.
 - Fix two residual issues found during testing (patches 2/3, 3/3).

Weiming Shi (3):
  tipc: fix NULL deref in tipc_named_node_up() on empty publication list
  tipc: fix NULL deref in deferred bulk distribution on publish failure
  tipc: fix node reference leak when defer work is already pending

 net/tipc/core.c       |  1 +
 net/tipc/core.h       |  2 ++
 net/tipc/name_distr.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++----
 net/tipc/name_distr.h |  3 ++-
 net/tipc/net.c        |  5 ++++-
 net/tipc/node.c       | 35 ++++++++++++++++++++++++++++++--
 6 files changed, 97 insertions(+), 8 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH net v7 1/3] tipc: fix NULL deref in tipc_named_node_up() on empty publication list
From: Weiming Shi @ 2026-07-18  9:09 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion
  Cc: Xiang Mei, Weiming Shi, kernel test robot, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Hoang Huu Le, linux-kernel
In-Reply-To: <20260718090931.724303-1-bestswngs@gmail.com>

[The defer-to-workqueue approach is by Tung Nguyen, suggested by
 Jon Maloy on the thread as the replacement for the item-less bulk
 approach. Since the RFC only exists as an inline diff in the thread,
 it is folded into this series so the fix is self-contained.]

named_distribute() stamps the last_bulk flag on the tail skb of the
publication list. When the list is empty no skb is enqueued and the
tail access dereferences NULL. tipc_named_node_up() hits this on an
empty cluster_scope, which happens with a node-id configuration where
cluster_scope is populated only later by tipc_net_finalize(). It is
reachable by an unprivileged user over a UDP bearer in a user+net
namespace. The reported crash:

 KASAN: null-ptr-deref in range [0x00000000000000d8-0x00000000000000df]
 RIP: 0010:tipc_named_node_up (net/tipc/name_distr.c:196)
  tipc_named_node_up (net/tipc/name_distr.c:196 net/tipc/name_distr.c:221)
  tipc_node_write_unlock (net/tipc/node.c:428)
  tipc_rcv (net/tipc/node.c:2185)
  tipc_udp_recv (net/tipc/udp_media.c:392)
 Kernel panic - not syncing: Fatal exception in interrupt

When cluster_scope is empty at node-up, defer the bulk distribution
to a workqueue and wait for tipc_net_finalize() to publish the
node-state name, so named_distribute() always runs on a non-empty
list. On allocation failure, purge the partially built queue and
bring the link down so the bulk distribution restarts when the link
comes up again.

Fixes: cad2929dc432 ("tipc: update a binding service via broadcast")
Reported-by: Xiang Mei <xmei5@asu.edu>
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202607180730.TwVgASDI-lkp@intel.com/
Signed-off-by: Tung Nguyen <tung.quang.nguyen@est.tech>
Tested-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
 net/tipc/core.c       |  1 +
 net/tipc/core.h       |  2 ++
 net/tipc/name_distr.c | 48 +++++++++++++++++++++++++++++++++++++++----
 net/tipc/name_distr.h |  3 ++-
 net/tipc/net.c        |  2 ++
 net/tipc/node.c       | 34 ++++++++++++++++++++++++++++--
 6 files changed, 83 insertions(+), 7 deletions(-)

diff --git a/net/tipc/core.c b/net/tipc/core.c
index 434e70eabe08..ce164509d9e2 100644
--- a/net/tipc/core.c
+++ b/net/tipc/core.c
@@ -60,6 +60,7 @@ static int __net_init tipc_init_net(struct net *net)
 	tn->trial_addr = 0;
 	tn->addr_trial_end = 0;
 	tn->capabilities = TIPC_NODE_CAPABILITIES;
+	atomic_set(&tn->finalized, 0);
 	INIT_WORK(&tn->work, tipc_net_finalize_work);
 	memset(tn->node_id, 0, sizeof(tn->node_id));
 	memset(tn->node_id_string, 0, sizeof(tn->node_id_string));
diff --git a/net/tipc/core.h b/net/tipc/core.h
index 9ce5f9ff6cc0..76768844c808 100644
--- a/net/tipc/core.h
+++ b/net/tipc/core.h
@@ -145,6 +145,8 @@ struct tipc_net {
 	struct work_struct work;
 	/* The numbers of work queues in schedule */
 	atomic_t wq_count;
+	/* flag to indicate work has finished */
+	atomic_t finalized;
 };
 
 static inline struct tipc_net *tipc_net(struct net *net)
diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c
index 190b49c5cbc3..b764274df758 100644
--- a/net/tipc/name_distr.c
+++ b/net/tipc/name_distr.c
@@ -147,7 +147,7 @@ struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *p)
  * @pls: linked list of publication items to be packed into buffer chain
  * @seqno: sequence number for this message
  */
-static void named_distribute(struct net *net, struct sk_buff_head *list,
+static int named_distribute(struct net *net, struct sk_buff_head *list,
 			     u32 dnode, struct list_head *pls, u16 seqno)
 {
 	struct publication *publ;
@@ -164,8 +164,9 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
 			skb = named_prepare_buf(net, PUBLICATION, msg_rem,
 						dnode);
 			if (!skb) {
+				__skb_queue_purge(list);
 				pr_warn("Bulk publication failure\n");
-				return;
+				return 1;
 			}
 			hdr = buf_msg(skb);
 			msg_set_bc_ack_invalid(hdr, true);
@@ -195,6 +196,8 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
 	hdr = buf_msg(skb_peek_tail(list));
 	msg_set_last_bulk(hdr);
 	msg_set_named_seqno(hdr, seqno);
+
+	return 0;
 }
 
 /**
@@ -203,7 +206,7 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
  * @dnode: destination node
  * @capabilities: peer node's capabilities
  */
-void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
+int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
 {
 	struct name_table *nt = tipc_name_table(net);
 	struct tipc_net *tn = tipc_net(net);
@@ -218,9 +221,46 @@ void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
 	spin_unlock_bh(&tn->nametbl_lock);
 
 	read_lock_bh(&nt->cluster_scope_lock);
-	named_distribute(net, &head, dnode, &nt->cluster_scope, seqno);
+	/* tipc_net_finalize_work() has not finished inserting self address to
+	 * name table yet.
+	 */
+	if (unlikely(list_empty(&nt->cluster_scope))) {
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return 1;
+	}
+
+	if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return -ENOBUFS;
+	}
+
 	tipc_node_xmit(net, &head, dnode, 0);
 	read_unlock_bh(&nt->cluster_scope_lock);
+	return 0;
+}
+
+int tipc_named_dist_cluster_scope(struct net *net, u32 dnode)
+{
+	struct name_table *nt = tipc_name_table(net);
+	struct tipc_net *tn = tipc_net(net);
+	struct sk_buff_head head;
+	u16 seqno;
+
+	__skb_queue_head_init(&head);
+	wait_var_event(&tn->finalized, atomic_read(&tn->finalized));
+	spin_lock_bh(&tn->nametbl_lock);
+	seqno = nt->snd_nxt;
+	spin_unlock_bh(&tn->nametbl_lock);
+
+	read_lock_bh(&nt->cluster_scope_lock);
+	if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return -ENOBUFS;
+	}
+	tipc_node_xmit(net, &head, dnode, 0);
+	read_unlock_bh(&nt->cluster_scope_lock);
+
+	return 0;
 }
 
 /**
diff --git a/net/tipc/name_distr.h b/net/tipc/name_distr.h
index c677f6f082df..cadf4e8c3e66 100644
--- a/net/tipc/name_distr.h
+++ b/net/tipc/name_distr.h
@@ -69,7 +69,8 @@ struct distr_item {
 
 struct sk_buff *tipc_named_publish(struct net *net, struct publication *publ);
 struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *publ);
-void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
+int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
+int tipc_named_dist_cluster_scope(struct net *net, u32 dnode);
 void tipc_named_rcv(struct net *net, struct sk_buff_head *namedq,
 		    u16 *rcv_nxt, bool *open);
 void tipc_named_reinit(struct net *net);
diff --git a/net/tipc/net.c b/net/tipc/net.c
index 7e65d0b0c4a8..4c144e720ac1 100644
--- a/net/tipc/net.c
+++ b/net/tipc/net.c
@@ -139,6 +139,8 @@ static void tipc_net_finalize(struct net *net, u32 addr)
 	tipc_sk_reinit(net);
 	tipc_mon_reinit_self(net);
 	tipc_nametbl_publish(net, &ua, &sk, addr);
+	atomic_inc(&tn->finalized);
+	wake_up_var(&tn->finalized);
 }
 
 void tipc_net_finalize_work(struct work_struct *work)
diff --git a/net/tipc/node.c b/net/tipc/node.c
index 97aa970a0d83..f757722d15be 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -111,6 +111,7 @@ struct tipc_bclink_entry {
  * @peer_net: peer's net namespace
  * @peer_hash_mix: hash for this peer (FIXME)
  * @crypto_rx: RX crypto handler
+ * @work: work item for bulk distribution of cluster scope publications
  */
 struct tipc_node {
 	u32 addr;
@@ -145,6 +146,7 @@ struct tipc_node {
 #ifdef CONFIG_TIPC_CRYPTO
 	struct tipc_crypto *crypto_rx;
 #endif
+	struct work_struct work;
 };
 
 /* Node FSM states and events:
@@ -303,6 +305,7 @@ static void tipc_node_free(struct rcu_head *rp)
 #ifdef CONFIG_TIPC_CRYPTO
 	tipc_crypto_stop(&n->crypto_rx);
 #endif
+	cancel_work_sync(&n->work);
 	kfree(n);
 }
 
@@ -393,6 +396,19 @@ static void tipc_node_write_unlock_fast(struct tipc_node *n)
 	write_unlock_bh(&n->lock);
 }
 
+static void tipc_node_dist_bulk(struct work_struct *work)
+{
+	struct tipc_node *node = container_of(work, struct tipc_node, work);
+
+	if (tipc_named_dist_cluster_scope(node->net, node->addr) < 0) {
+		u32 bearer_id = node->link_id & 0xffff;
+
+		tipc_node_link_down(node, bearer_id, false);
+	}
+
+	tipc_node_put(node);
+}
+
 static void tipc_node_write_unlock(struct tipc_node *n)
 	__releases(n->lock)
 {
@@ -424,8 +440,21 @@ static void tipc_node_write_unlock(struct tipc_node *n)
 	if (flags & TIPC_NOTIFY_NODE_DOWN)
 		tipc_publ_notify(net, publ_list, node, n->capabilities);
 
-	if (flags & TIPC_NOTIFY_NODE_UP)
-		tipc_named_node_up(net, node, n->capabilities);
+	if (flags & TIPC_NOTIFY_NODE_UP) {
+		int rc = 0;
+
+		rc = tipc_named_node_up(net, node, n->capabilities);
+		/* Defer bulk distribution to work queue */
+		if (rc > 0) {
+			tipc_node_get(n);
+			schedule_work(&n->work);
+		} else if (rc < 0) {
+			/* Bring the link down to start over bulk distribution
+			 * when the link is up again.
+			 */
+			tipc_node_link_down(n, bearer_id, false);
+		}
+	}
 
 	if (flags & TIPC_NOTIFY_LINK_UP) {
 		tipc_mon_peer_up(net, node, bearer_id);
@@ -564,6 +593,7 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
 	INIT_LIST_HEAD(&n->list);
 	INIT_LIST_HEAD(&n->publ_list);
 	INIT_LIST_HEAD(&n->conn_sks);
+	INIT_WORK(&n->work, tipc_node_dist_bulk);
 	skb_queue_head_init(&n->bc_entry.namedq);
 	skb_queue_head_init(&n->bc_entry.inputq1);
 	__skb_queue_head_init(&n->bc_entry.arrvq);
-- 
2.43.0


^ permalink raw reply related

* [PATCH net v7 2/3] tipc: fix NULL deref in deferred bulk distribution on publish failure
From: Weiming Shi @ 2026-07-18  9:09 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion
  Cc: Xiang Mei, Weiming Shi, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, kernel test robot,
	Hoang Huu Le, linux-kernel
In-Reply-To: <20260718090931.724303-1-bestswngs@gmail.com>

tipc_net_finalize() does not check the return value of
tipc_nametbl_publish(). If the publish fails, for example on a
GFP_ATOMIC allocation failure, the node state name never lands in
cluster_scope, but tn->finalized is still set. A worker deferred by
tipc_named_node_up() then wakes and calls named_distribute() with an
empty list. That replays the same unguarded buf_msg(skb_peek_tail(list))
tail stamp, this time on the tipc_node_dist_bulk workqueue:

 KASAN: null-ptr-deref in range [0x00000000000000c8-0x00000000000000cf]
 RIP: 0010:named_distribute (net/tipc/name_distr.c:200)
 Workqueue: events tipc_node_dist_bulk
 Call Trace:
  tipc_named_dist_cluster_scope (net/tipc/name_distr.c:267)
  tipc_node_dist_bulk (net/tipc/node.c:403)
  process_one_work
  worker_thread
 Kernel panic - not syncing: Fatal exception in interrupt

Check the publish result and warn on failure, but still set finalized,
otherwise deferred workers would sleep forever. In
tipc_named_dist_cluster_scope() re-check cluster_scope after the wait
and skip the distribution when it is empty. This is a permanent
condition, so return 0 instead of an error, otherwise the link would
be bounced forever. Also guard the tail stamp in named_distribute()
itself, so a caller that misses the precondition gets a warning and a
link reset through the existing -ENOBUFS path instead of a crash.

Reproducing this needs an allocation failure during finalize, so I
verified it by stubbing out the publish call: both nodes log the
failure, the workers skip the distribution, no crash, no link flap.
The normal path is unchanged with the same two-node test.

Fixes: cad2929dc432 ("tipc: update a binding service via broadcast")
Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
 net/tipc/name_distr.c | 11 +++++++++++
 net/tipc/net.c        |  3 ++-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c
index b764274df758..5b0fb09226fc 100644
--- a/net/tipc/name_distr.c
+++ b/net/tipc/name_distr.c
@@ -193,6 +193,10 @@ static int named_distribute(struct net *net, struct sk_buff_head *list,
 		skb_trim(skb, INT_H_SIZE + (msg_dsz - msg_rem));
 		__skb_queue_tail(list, skb);
 	}
+	if (skb_queue_empty(list)) {
+		pr_warn("Bulk publication list empty, nothing to distribute\n");
+		return 1;
+	}
 	hdr = buf_msg(skb_peek_tail(list));
 	msg_set_last_bulk(hdr);
 	msg_set_named_seqno(hdr, seqno);
@@ -253,6 +257,13 @@ int tipc_named_dist_cluster_scope(struct net *net, u32 dnode)
 	spin_unlock_bh(&tn->nametbl_lock);
 
 	read_lock_bh(&nt->cluster_scope_lock);
+	if (unlikely(list_empty(&nt->cluster_scope))) {
+		/* finalize is done but nothing was published (publish
+		 * failed): a permanent state, nothing to synchronize.
+		 */
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return 0;
+	}
 	if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
 		read_unlock_bh(&nt->cluster_scope_lock);
 		return -ENOBUFS;
diff --git a/net/tipc/net.c b/net/tipc/net.c
index 4c144e720ac1..2aa8812c551a 100644
--- a/net/tipc/net.c
+++ b/net/tipc/net.c
@@ -138,7 +138,8 @@ static void tipc_net_finalize(struct net *net, u32 addr)
 	tipc_named_reinit(net);
 	tipc_sk_reinit(net);
 	tipc_mon_reinit_self(net);
-	tipc_nametbl_publish(net, &ua, &sk, addr);
+	if (!tipc_nametbl_publish(net, &ua, &sk, addr))
+		pr_warn("Failed to publish own node state\n");
 	atomic_inc(&tn->finalized);
 	wake_up_var(&tn->finalized);
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH net v7 3/3] tipc: fix node reference leak when defer work is already pending
From: Weiming Shi @ 2026-07-18  9:09 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion
  Cc: Xiang Mei, Weiming Shi, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel
In-Reply-To: <20260718090931.724303-1-bestswngs@gmail.com>

In tipc_node_write_unlock(), TIPC_NOTIFY_NODE_UP with an empty
cluster_scope takes a node reference and schedules n->work. If the
link flaps down and up while that work is still pending, the next
NODE_UP takes another reference, but schedule_work() returns false
and the extra reference is never dropped. The tipc_node structure
leaks.

Verified by flapping the bearer while the work is pending: one
reference is leaked per repeated NODE_UP, while the work is put only
once when it finally runs.

Drop the reference when the work was already queued.

Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
 net/tipc/node.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/tipc/node.c b/net/tipc/node.c
index f757722d15be..f826347b5870 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -447,7 +447,8 @@ static void tipc_node_write_unlock(struct tipc_node *n)
 		/* Defer bulk distribution to work queue */
 		if (rc > 0) {
 			tipc_node_get(n);
-			schedule_work(&n->work);
+			if (!schedule_work(&n->work))
+				tipc_node_put(n);
 		} else if (rc < 0) {
 			/* Bring the link down to start over bulk distribution
 			 * when the link is up again.
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH iproute2-next] ipmaddr: use RTM_GETMULTICAST to list multicast addresses
From: Yuyang Huang @ 2026-07-18  9:11 UTC (permalink / raw)
  To: David Ahern; +Cc: netdev
In-Reply-To: <CA+7S2v+BUmfHQpS7U8xhdiHr=EMt-S5F+oEPFiBLXQUOuX6Z6A@mail.gmail.com>

> The kernel replies to a RTM_GETMULTICAST dump request with
> RTM_NEWMULTICAST messages — it never sends RTM_GETMULTICAST in a
> response.

Hmm I just realized It's the other way around. The dump path puts
RTM_GETMULTICAST on the wire:

net/ipv4/devinet.c:
      return inet_dump_addr(skb, cb, RTM_GETMULTICAST);

IPv6 does the same (in6_dump_addrs() sets fillargs->event =
RTM_GETMULTICAST). RTM_NEWMULTICAST is only used for the
RTNLGRP_*_MCADDR notifications, never in dump replies.

So my v2 patch has a bug, I will send the v3 patch to fix it.

Thanks,

Yuyang

^ permalink raw reply

* [PATCH net v8 0/3] tipc: fix NULL deref in tipc_named_node_up() on empty publication list
From: Weiming Shi @ 2026-07-18  9:25 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion; +Cc: Xiang Mei, Weiming Shi

This series continues the fix for the NULL dereference in
tipc_named_node_up() on an empty publication list.

Patch 1/3 carries Tung Nguyen's defer-to-workqueue approach, which
he posted on the thread as the replacement for the item-less bulk
from v2 and asked us to test. Tung's RFC only exists as an inline
diff in the thread, so I folded it into this series, keeping his
Signed-off-by. I tested it in our two-node QEMU setup (veth pair,
UDP bearers, node-id addressing), both with an unprivileged user
namespace and as root: the unpatched kernel panics on the first run
of the same reproducer, the patched one distributes normally with a
non-empty list.

While testing we found two residual issues in the approach, fixed by
patches 2/3 and 3/3.

Patch 2/3: tipc_net_finalize() does not check the return value of
tipc_nametbl_publish(). If the publish fails, for example on a
GFP_ATOMIC allocation failure, the node is finalized but cluster_scope
stays empty. The deferred worker then calls named_distribute() with
an empty list and hits the same NULL dereference, this time on the
workqueue. With this patch the worker re-checks the list and skips
cleanly, no crash and no link flap. The tail stamp in
named_distribute() also gets an empty-queue guard.

Patch 3/3: a repeated NODE_UP while the bulk work is pending takes a
node reference that is never dropped, because schedule_work() returns
false when the work is already queued. Found by flapping the bearer
during the defer window. One reference is leaked per repeated
NODE_UP.

Changes in v8:
 - Attribution text fix only, no code changes. The defer approach
   is Tung Nguyen's work; earlier versions wrongly credited
   Jon Maloy.

Changes in v7:
 - Patch 1/3: add the missing kernel-doc description for the new work
   member of struct tipc_node, fixing the W=1 build warning reported
   by the kernel test robot. No code change.
 - https://lore.kernel.org/all/20260718090931.724303-1-bestswngs@gmail.com/

Changes in v6:
 - Make the series self-contained: fold Tung Nguyen's base patch into
   the series (1/3), keeping his Signed-off-by. The version sent as
   v5 only carried the two follow-ups and depended on his patch from
   the thread; the code changes in 2/3 and 3/3 are unchanged from
   that version.
 - https://lore.kernel.org/all/20260717185701.2828080-1-bestswngs@gmail.com/

Changes in v5:
 - Replace the item-less bulk approach with Tung Nguyen's
   defer-to-workqueue RFC, which fixes the reported bug in our
   testing.
 - Fix two residual issues found during testing (patches 2/3, 3/3).

Weiming Shi (3):
  tipc: fix NULL deref in tipc_named_node_up() on empty publication list
  tipc: fix NULL deref in deferred bulk distribution on publish failure
  tipc: fix node reference leak when defer work is already pending

 net/tipc/core.c       |  1 +
 net/tipc/core.h       |  2 ++
 net/tipc/name_distr.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++----
 net/tipc/name_distr.h |  3 ++-
 net/tipc/net.c        |  5 ++++-
 net/tipc/node.c       | 35 ++++++++++++++++++++++++++++++--
 6 files changed, 97 insertions(+), 8 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH net v8 1/3] tipc: fix NULL deref in tipc_named_node_up() on empty publication list
From: Weiming Shi @ 2026-07-18  9:25 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion
  Cc: Xiang Mei, Weiming Shi, kernel test robot, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Hoang Huu Le, linux-kernel
In-Reply-To: <20260718092544.785289-1-bestswngs@gmail.com>

[The defer-to-workqueue approach is by Tung Nguyen. He posted it
 on the thread and asked us to test it, as the replacement for the
 item-less bulk approach. Since the RFC only exists as an inline
 diff in the thread, it is folded into this series so the fix is
 self-contained.]

named_distribute() stamps the last_bulk flag on the tail skb of the
publication list. When the list is empty no skb is enqueued and the
tail access dereferences NULL. tipc_named_node_up() hits this on an
empty cluster_scope, which happens with a node-id configuration where
cluster_scope is populated only later by tipc_net_finalize(). It is
reachable by an unprivileged user over a UDP bearer in a user+net
namespace. The reported crash:

 KASAN: null-ptr-deref in range [0x00000000000000d8-0x00000000000000df]
 RIP: 0010:tipc_named_node_up (net/tipc/name_distr.c:196)
  tipc_named_node_up (net/tipc/name_distr.c:196 net/tipc/name_distr.c:221)
  tipc_node_write_unlock (net/tipc/node.c:428)
  tipc_rcv (net/tipc/node.c:2185)
  tipc_udp_recv (net/tipc/udp_media.c:392)
 Kernel panic - not syncing: Fatal exception in interrupt

When cluster_scope is empty at node-up, defer the bulk distribution
to a workqueue and wait for tipc_net_finalize() to publish the
node-state name, so named_distribute() always runs on a non-empty
list. On allocation failure, purge the partially built queue and
bring the link down so the bulk distribution restarts when the link
comes up again.

Fixes: cad2929dc432 ("tipc: update a binding service via broadcast")
Reported-by: Xiang Mei <xmei5@asu.edu>
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202607180730.TwVgASDI-lkp@intel.com/
Signed-off-by: Tung Nguyen <tung.quang.nguyen@est.tech>
Tested-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
 net/tipc/core.c       |  1 +
 net/tipc/core.h       |  2 ++
 net/tipc/name_distr.c | 48 +++++++++++++++++++++++++++++++++++++++----
 net/tipc/name_distr.h |  3 ++-
 net/tipc/net.c        |  2 ++
 net/tipc/node.c       | 34 ++++++++++++++++++++++++++++--
 6 files changed, 83 insertions(+), 7 deletions(-)

diff --git a/net/tipc/core.c b/net/tipc/core.c
index 434e70eabe08..ce164509d9e2 100644
--- a/net/tipc/core.c
+++ b/net/tipc/core.c
@@ -60,6 +60,7 @@ static int __net_init tipc_init_net(struct net *net)
 	tn->trial_addr = 0;
 	tn->addr_trial_end = 0;
 	tn->capabilities = TIPC_NODE_CAPABILITIES;
+	atomic_set(&tn->finalized, 0);
 	INIT_WORK(&tn->work, tipc_net_finalize_work);
 	memset(tn->node_id, 0, sizeof(tn->node_id));
 	memset(tn->node_id_string, 0, sizeof(tn->node_id_string));
diff --git a/net/tipc/core.h b/net/tipc/core.h
index 9ce5f9ff6cc0..76768844c808 100644
--- a/net/tipc/core.h
+++ b/net/tipc/core.h
@@ -145,6 +145,8 @@ struct tipc_net {
 	struct work_struct work;
 	/* The numbers of work queues in schedule */
 	atomic_t wq_count;
+	/* flag to indicate work has finished */
+	atomic_t finalized;
 };
 
 static inline struct tipc_net *tipc_net(struct net *net)
diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c
index 190b49c5cbc3..b764274df758 100644
--- a/net/tipc/name_distr.c
+++ b/net/tipc/name_distr.c
@@ -147,7 +147,7 @@ struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *p)
  * @pls: linked list of publication items to be packed into buffer chain
  * @seqno: sequence number for this message
  */
-static void named_distribute(struct net *net, struct sk_buff_head *list,
+static int named_distribute(struct net *net, struct sk_buff_head *list,
 			     u32 dnode, struct list_head *pls, u16 seqno)
 {
 	struct publication *publ;
@@ -164,8 +164,9 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
 			skb = named_prepare_buf(net, PUBLICATION, msg_rem,
 						dnode);
 			if (!skb) {
+				__skb_queue_purge(list);
 				pr_warn("Bulk publication failure\n");
-				return;
+				return 1;
 			}
 			hdr = buf_msg(skb);
 			msg_set_bc_ack_invalid(hdr, true);
@@ -195,6 +196,8 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
 	hdr = buf_msg(skb_peek_tail(list));
 	msg_set_last_bulk(hdr);
 	msg_set_named_seqno(hdr, seqno);
+
+	return 0;
 }
 
 /**
@@ -203,7 +206,7 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
  * @dnode: destination node
  * @capabilities: peer node's capabilities
  */
-void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
+int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
 {
 	struct name_table *nt = tipc_name_table(net);
 	struct tipc_net *tn = tipc_net(net);
@@ -218,9 +221,46 @@ void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
 	spin_unlock_bh(&tn->nametbl_lock);
 
 	read_lock_bh(&nt->cluster_scope_lock);
-	named_distribute(net, &head, dnode, &nt->cluster_scope, seqno);
+	/* tipc_net_finalize_work() has not finished inserting self address to
+	 * name table yet.
+	 */
+	if (unlikely(list_empty(&nt->cluster_scope))) {
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return 1;
+	}
+
+	if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return -ENOBUFS;
+	}
+
 	tipc_node_xmit(net, &head, dnode, 0);
 	read_unlock_bh(&nt->cluster_scope_lock);
+	return 0;
+}
+
+int tipc_named_dist_cluster_scope(struct net *net, u32 dnode)
+{
+	struct name_table *nt = tipc_name_table(net);
+	struct tipc_net *tn = tipc_net(net);
+	struct sk_buff_head head;
+	u16 seqno;
+
+	__skb_queue_head_init(&head);
+	wait_var_event(&tn->finalized, atomic_read(&tn->finalized));
+	spin_lock_bh(&tn->nametbl_lock);
+	seqno = nt->snd_nxt;
+	spin_unlock_bh(&tn->nametbl_lock);
+
+	read_lock_bh(&nt->cluster_scope_lock);
+	if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return -ENOBUFS;
+	}
+	tipc_node_xmit(net, &head, dnode, 0);
+	read_unlock_bh(&nt->cluster_scope_lock);
+
+	return 0;
 }
 
 /**
diff --git a/net/tipc/name_distr.h b/net/tipc/name_distr.h
index c677f6f082df..cadf4e8c3e66 100644
--- a/net/tipc/name_distr.h
+++ b/net/tipc/name_distr.h
@@ -69,7 +69,8 @@ struct distr_item {
 
 struct sk_buff *tipc_named_publish(struct net *net, struct publication *publ);
 struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *publ);
-void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
+int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
+int tipc_named_dist_cluster_scope(struct net *net, u32 dnode);
 void tipc_named_rcv(struct net *net, struct sk_buff_head *namedq,
 		    u16 *rcv_nxt, bool *open);
 void tipc_named_reinit(struct net *net);
diff --git a/net/tipc/net.c b/net/tipc/net.c
index 7e65d0b0c4a8..4c144e720ac1 100644
--- a/net/tipc/net.c
+++ b/net/tipc/net.c
@@ -139,6 +139,8 @@ static void tipc_net_finalize(struct net *net, u32 addr)
 	tipc_sk_reinit(net);
 	tipc_mon_reinit_self(net);
 	tipc_nametbl_publish(net, &ua, &sk, addr);
+	atomic_inc(&tn->finalized);
+	wake_up_var(&tn->finalized);
 }
 
 void tipc_net_finalize_work(struct work_struct *work)
diff --git a/net/tipc/node.c b/net/tipc/node.c
index 97aa970a0d83..f757722d15be 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -111,6 +111,7 @@ struct tipc_bclink_entry {
  * @peer_net: peer's net namespace
  * @peer_hash_mix: hash for this peer (FIXME)
  * @crypto_rx: RX crypto handler
+ * @work: work item for bulk distribution of cluster scope publications
  */
 struct tipc_node {
 	u32 addr;
@@ -145,6 +146,7 @@ struct tipc_node {
 #ifdef CONFIG_TIPC_CRYPTO
 	struct tipc_crypto *crypto_rx;
 #endif
+	struct work_struct work;
 };
 
 /* Node FSM states and events:
@@ -303,6 +305,7 @@ static void tipc_node_free(struct rcu_head *rp)
 #ifdef CONFIG_TIPC_CRYPTO
 	tipc_crypto_stop(&n->crypto_rx);
 #endif
+	cancel_work_sync(&n->work);
 	kfree(n);
 }
 
@@ -393,6 +396,19 @@ static void tipc_node_write_unlock_fast(struct tipc_node *n)
 	write_unlock_bh(&n->lock);
 }
 
+static void tipc_node_dist_bulk(struct work_struct *work)
+{
+	struct tipc_node *node = container_of(work, struct tipc_node, work);
+
+	if (tipc_named_dist_cluster_scope(node->net, node->addr) < 0) {
+		u32 bearer_id = node->link_id & 0xffff;
+
+		tipc_node_link_down(node, bearer_id, false);
+	}
+
+	tipc_node_put(node);
+}
+
 static void tipc_node_write_unlock(struct tipc_node *n)
 	__releases(n->lock)
 {
@@ -424,8 +440,21 @@ static void tipc_node_write_unlock(struct tipc_node *n)
 	if (flags & TIPC_NOTIFY_NODE_DOWN)
 		tipc_publ_notify(net, publ_list, node, n->capabilities);
 
-	if (flags & TIPC_NOTIFY_NODE_UP)
-		tipc_named_node_up(net, node, n->capabilities);
+	if (flags & TIPC_NOTIFY_NODE_UP) {
+		int rc = 0;
+
+		rc = tipc_named_node_up(net, node, n->capabilities);
+		/* Defer bulk distribution to work queue */
+		if (rc > 0) {
+			tipc_node_get(n);
+			schedule_work(&n->work);
+		} else if (rc < 0) {
+			/* Bring the link down to start over bulk distribution
+			 * when the link is up again.
+			 */
+			tipc_node_link_down(n, bearer_id, false);
+		}
+	}
 
 	if (flags & TIPC_NOTIFY_LINK_UP) {
 		tipc_mon_peer_up(net, node, bearer_id);
@@ -564,6 +593,7 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
 	INIT_LIST_HEAD(&n->list);
 	INIT_LIST_HEAD(&n->publ_list);
 	INIT_LIST_HEAD(&n->conn_sks);
+	INIT_WORK(&n->work, tipc_node_dist_bulk);
 	skb_queue_head_init(&n->bc_entry.namedq);
 	skb_queue_head_init(&n->bc_entry.inputq1);
 	__skb_queue_head_init(&n->bc_entry.arrvq);
-- 
2.43.0


^ permalink raw reply related

* [PATCH net v8 2/3] tipc: fix NULL deref in deferred bulk distribution on publish failure
From: Weiming Shi @ 2026-07-18  9:25 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion
  Cc: Xiang Mei, Weiming Shi, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, kernel test robot,
	Hoang Huu Le, linux-kernel
In-Reply-To: <20260718092544.785289-1-bestswngs@gmail.com>

tipc_net_finalize() does not check the return value of
tipc_nametbl_publish(). If the publish fails, for example on a
GFP_ATOMIC allocation failure, the node state name never lands in
cluster_scope, but tn->finalized is still set. A worker deferred by
tipc_named_node_up() then wakes and calls named_distribute() with an
empty list. That replays the same unguarded buf_msg(skb_peek_tail(list))
tail stamp, this time on the tipc_node_dist_bulk workqueue:

 KASAN: null-ptr-deref in range [0x00000000000000c8-0x00000000000000cf]
 RIP: 0010:named_distribute (net/tipc/name_distr.c:200)
 Workqueue: events tipc_node_dist_bulk
 Call Trace:
  tipc_named_dist_cluster_scope (net/tipc/name_distr.c:267)
  tipc_node_dist_bulk (net/tipc/node.c:403)
  process_one_work
  worker_thread
 Kernel panic - not syncing: Fatal exception in interrupt

Check the publish result and warn on failure, but still set finalized,
otherwise deferred workers would sleep forever. In
tipc_named_dist_cluster_scope() re-check cluster_scope after the wait
and skip the distribution when it is empty. This is a permanent
condition, so return 0 instead of an error, otherwise the link would
be bounced forever. Also guard the tail stamp in named_distribute()
itself, so a caller that misses the precondition gets a warning and a
link reset through the existing -ENOBUFS path instead of a crash.

Reproducing this needs an allocation failure during finalize, so I
verified it by stubbing out the publish call: both nodes log the
failure, the workers skip the distribution, no crash, no link flap.
The normal path is unchanged with the same two-node test.

Fixes: cad2929dc432 ("tipc: update a binding service via broadcast")
Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
 net/tipc/name_distr.c | 11 +++++++++++
 net/tipc/net.c        |  3 ++-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c
index b764274df758..5b0fb09226fc 100644
--- a/net/tipc/name_distr.c
+++ b/net/tipc/name_distr.c
@@ -193,6 +193,10 @@ static int named_distribute(struct net *net, struct sk_buff_head *list,
 		skb_trim(skb, INT_H_SIZE + (msg_dsz - msg_rem));
 		__skb_queue_tail(list, skb);
 	}
+	if (skb_queue_empty(list)) {
+		pr_warn("Bulk publication list empty, nothing to distribute\n");
+		return 1;
+	}
 	hdr = buf_msg(skb_peek_tail(list));
 	msg_set_last_bulk(hdr);
 	msg_set_named_seqno(hdr, seqno);
@@ -253,6 +257,13 @@ int tipc_named_dist_cluster_scope(struct net *net, u32 dnode)
 	spin_unlock_bh(&tn->nametbl_lock);
 
 	read_lock_bh(&nt->cluster_scope_lock);
+	if (unlikely(list_empty(&nt->cluster_scope))) {
+		/* finalize is done but nothing was published (publish
+		 * failed): a permanent state, nothing to synchronize.
+		 */
+		read_unlock_bh(&nt->cluster_scope_lock);
+		return 0;
+	}
 	if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
 		read_unlock_bh(&nt->cluster_scope_lock);
 		return -ENOBUFS;
diff --git a/net/tipc/net.c b/net/tipc/net.c
index 4c144e720ac1..2aa8812c551a 100644
--- a/net/tipc/net.c
+++ b/net/tipc/net.c
@@ -138,7 +138,8 @@ static void tipc_net_finalize(struct net *net, u32 addr)
 	tipc_named_reinit(net);
 	tipc_sk_reinit(net);
 	tipc_mon_reinit_self(net);
-	tipc_nametbl_publish(net, &ua, &sk, addr);
+	if (!tipc_nametbl_publish(net, &ua, &sk, addr))
+		pr_warn("Failed to publish own node state\n");
 	atomic_inc(&tn->finalized);
 	wake_up_var(&tn->finalized);
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH net v8 3/3] tipc: fix node reference leak when defer work is already pending
From: Weiming Shi @ 2026-07-18  9:25 UTC (permalink / raw)
  To: Jon Maloy, Tung Nguyen, netdev, tipc-discussion
  Cc: Xiang Mei, Weiming Shi, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel
In-Reply-To: <20260718092544.785289-1-bestswngs@gmail.com>

In tipc_node_write_unlock(), TIPC_NOTIFY_NODE_UP with an empty
cluster_scope takes a node reference and schedules n->work. If the
link flaps down and up while that work is still pending, the next
NODE_UP takes another reference, but schedule_work() returns false
and the extra reference is never dropped. The tipc_node structure
leaks.

Verified by flapping the bearer while the work is pending: one
reference is leaked per repeated NODE_UP, while the work is put only
once when it finally runs.

Drop the reference when the work was already queued.

Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
 net/tipc/node.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/tipc/node.c b/net/tipc/node.c
index f757722d15be..f826347b5870 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -447,7 +447,8 @@ static void tipc_node_write_unlock(struct tipc_node *n)
 		/* Defer bulk distribution to work queue */
 		if (rc > 0) {
 			tipc_node_get(n);
-			schedule_work(&n->work);
+			if (!schedule_work(&n->work))
+				tipc_node_put(n);
 		} else if (rc < 0) {
 			/* Bring the link down to start over bulk distribution
 			 * when the link is up again.
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net v4 2/2] tipc: fix NULL deref in tipc_named_node_up() on empty publication list
From: Weiming Shi @ 2026-07-18  9:28 UTC (permalink / raw)
  To: Tung Quang Nguyen
  Cc: netdev@vger.kernel.org, tipc-discussion@lists.sourceforge.net,
	linux-kernel@vger.kernel.org, xmei5@asu.edu, Jon Maloy,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
In-Reply-To: <CANgPUi31G4DvrBPDRGniPDzWTUKo_3HQL8W1nq8BMmtnp8Fa+g@mail.gmail.com>

Weiming Shi <bestswngs@gmail.com> 于2026年7月18日周六 03:00写道:
>
> Weiming Shi <bestswngs@gmail.com> 于2026年7月18日周六 02:34写道:
> >
> > Tung Quang Nguyen <tung.quang.nguyen@est.tech> 于2026年7月17日周五 17:48写道:
> > >
> > > >Subject: [PATCH net v4 2/2] tipc: fix NULL deref in tipc_named_node_up() on
> > > >empty publication list
> > > >
> > > >named_distribute() ends by stamping the last_bulk flag on the tail skb via
> > > >buf_msg(skb_peek_tail(list)). When the publication list is empty no skb is
> > > >enqueued, skb_peek_tail() returns NULL, and buf_msg(NULL) is dereferenced.
> > > >
> > > >tipc_named_node_up() runs this on &nt->cluster_scope. With a node-id
> > > >configuration cluster_scope is populated only later by tipc_net_finalize(), so a
> > > >peer link that comes up first reaches named_distribute() with an empty list. It
> > > >is reachable by an unprivileged user (TIPC genl ops use
> > > >GENL_UNS_ADMIN_PERM) over a UDP bearer in a user+net namespace:
> > > >
> > > > KASAN: null-ptr-deref in range [0x00000000000000d8-0x00000000000000df]
> > > > RIP: 0010:tipc_named_node_up (net/tipc/name_distr.c:196)
> > > >  tipc_named_node_up (net/tipc/name_distr.c:196 net/tipc/name_distr.c:221)
> > > >  tipc_node_write_unlock (net/tipc/node.c:428)
> > > >  tipc_rcv (net/tipc/node.c:2185)
> > > >  tipc_udp_recv (net/tipc/udp_media.c:392)  Kernel panic - not syncing: Fatal
> > > >exception in interrupt
> > > >
> > > >The peer holds back this node's later name updates until it sees a bulk with the
> > > >last_bulk flag, so simply skipping the send would stall it. Emit an item-less bulk
> > > >when the publication list is empty, so the peer still receives the last_bulk flag
> > > >and opens.
> > > >
> > > >Fixes: cad2929dc432 ("tipc: update a binding service via broadcast")
> > > >Reported-by: Xiang Mei <xmei5@asu.edu>
> > > >Assisted-by: Claude:claude-opus-4-8
> > > >Signed-off-by: Weiming Shi <bestswngs@gmail.com>
> > > >---
> > > > net/tipc/name_distr.c | 14 ++++++++++++++
> > > > 1 file changed, 14 insertions(+)
> > > >
> > > >diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c index
> > > >ba4f4906e13b..a8bb7bd101ea 100644
> > > >--- a/net/tipc/name_distr.c
> > > >+++ b/net/tipc/name_distr.c
> > > >@@ -192,6 +192,20 @@ static void named_distribute(struct net *net, struct
> > > >sk_buff_head *list,
> > > >               skb_trim(skb, INT_H_SIZE + (msg_dsz - msg_rem));
> > > >               __skb_queue_tail(list, skb);
> > > >       }
> > > >+
> > > >+      if (skb_queue_empty(list)) {
> > > >+              skb = named_prepare_buf(net, PUBLICATION, 0, dnode);
> > > >+              if (!skb) {
> > > >+                      pr_warn("Bulk publication failure\n");
> > > >+                      return;
> > > >+              }
> > > >+              hdr = buf_msg(skb);
> > > >+              msg_set_bc_ack_invalid(hdr, true);
> > > >+              msg_set_bulk(hdr);
> > > >+              msg_set_non_legacy(hdr);
> > > >+              __skb_queue_tail(list, skb);
> > > >+      }
> > > As I explained before, this approach is wrong because
> > > 1. It does not handle memory allocation failure.
> > > 2. It breaks receiving peer by  sending non-data message to that peer in case skb is not NULL.
> > >
> > > Could you please test below patch to see if it fixes the NULL dereference issue you reported ?
> >
> > Hi ,
> > Tested your patch, it fixes the NULL dereference I reported. No more
> > panic with an empty cluster_scope .
> >
> > One new bug found during testing: if tipc_nametbl_publish() fails in
> > tipc_net_finalize(), the node is
> > still marked finalized, so the deferred worker wakes up and calls
> > named_distribute() with an empty list,
> > hitting the same NULL dereference.
> >
> > I have the fix ready and sent it out:
> >
> > https://lore.kernel.org/all/20260717183047.2725959-1-bestswngs@gmail.com/
> > https://lore.kernel.org/all/20260717183047.2725959-2-bestswngs@gmail.com/
> > https://lore.kernel.org/all/20260717183047.2725959-3-bestswngs@gmail.com/
> >
> > Thanks,
> > Weiming Shi
> >
>
> Hi,
> The v5 I sent earlier was incomplete. It only carried the two
> follow-up patches and depended on Tung's
> patch from this thread as its base, which made the series hard to
> apply on its own.
>
> I have resent the series as v6:
> https://lore.kernel.org/all/20260717185701.2828080-1-bestswngs@gmail.com/
>
> Sorry for the noise.

Sorry for the noise. This is the latest version, with some modifications made.

https://lore.kernel.org/all/20260718092544.785289-1-bestswngs@gmail.com/
> > >
> > > ---
> > >  net/tipc/core.c       |  1 +
> > >  net/tipc/core.h       |  2 ++
> > >  net/tipc/name_distr.c | 48 +++++++++++++++++++++++++++++++++++++++----
> > >  net/tipc/name_distr.h |  3 ++-
> > >  net/tipc/net.c        |  2 ++
> > >  net/tipc/node.c       | 34 ++++++++++++++++++++++++++++--
> > >  6 files changed, 83 insertions(+), 7 deletions(-)
> > >
> > > diff --git a/net/tipc/core.c b/net/tipc/core.c
> > > index 315975c3be81..9e81be4f01cf 100644
> > > --- a/net/tipc/core.c
> > > +++ b/net/tipc/core.c
> > > @@ -61,6 +61,7 @@ static int __net_init tipc_init_net(struct net *net)
> > >         tn->trial_addr = 0;
> > >         tn->addr_trial_end = 0;
> > >         tn->capabilities = TIPC_NODE_CAPABILITIES;
> > > +       atomic_set(&tn->finalized, 0);
> > >         INIT_WORK(&tn->work, tipc_net_finalize_work);
> > >         memset(tn->node_id, 0, sizeof(tn->node_id));
> > >         memset(tn->node_id_string, 0, sizeof(tn->node_id_string));
> > > diff --git a/net/tipc/core.h b/net/tipc/core.h
> > > index 9ce5f9ff6cc0..76768844c808 100644
> > > --- a/net/tipc/core.h
> > > +++ b/net/tipc/core.h
> > > @@ -145,6 +145,8 @@ struct tipc_net {
> > >         struct work_struct work;
> > >         /* The numbers of work queues in schedule */
> > >         atomic_t wq_count;
> > > +       /* flag to indicate work has finished */
> > > +       atomic_t finalized;
> > >  };
> > >
> > >  static inline struct tipc_net *tipc_net(struct net *net)
> > > diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c
> > > index ba5f4906e13b..8a1692dbd243 100644
> > > --- a/net/tipc/name_distr.c
> > > +++ b/net/tipc/name_distr.c
> > > @@ -147,7 +147,7 @@ struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *p)
> > >   * @pls: linked list of publication items to be packed into buffer chain
> > >   * @seqno: sequence number for this message
> > >   */
> > > -static void named_distribute(struct net *net, struct sk_buff_head *list,
> > > +static int named_distribute(struct net *net, struct sk_buff_head *list,
> > >                              u32 dnode, struct list_head *pls, u16 seqno)
> > >  {
> > >         struct publication *publ;
> > > @@ -164,8 +164,9 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
> > >                         skb = named_prepare_buf(net, PUBLICATION, msg_rem,
> > >                                                 dnode);
> > >                         if (!skb) {
> > > +                               __skb_queue_purge(list);
> > >                                 pr_warn("Bulk publication failure\n");
> > > -                               return;
> > > +                               return 1;
> > >                         }
> > >                         hdr = buf_msg(skb);
> > >                         msg_set_bc_ack_invalid(hdr, true);
> > > @@ -195,6 +196,8 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
> > >         hdr = buf_msg(skb_peek_tail(list));
> > >         msg_set_last_bulk(hdr);
> > >         msg_set_named_seqno(hdr, seqno);
> > > +
> > > +       return 0;
> > >  }
> > >
> > >  /**
> > > @@ -203,7 +206,7 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
> > >   * @dnode: destination node
> > >   * @capabilities: peer node's capabilities
> > >   */
> > > -void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
> > > +int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
> > >  {
> > >         struct name_table *nt = tipc_name_table(net);
> > >         struct tipc_net *tn = tipc_net(net);
> > > @@ -218,9 +221,46 @@ void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
> > >         spin_unlock_bh(&tn->nametbl_lock);
> > >
> > >         read_lock_bh(&nt->cluster_scope_lock);
> > > -       named_distribute(net, &head, dnode, &nt->cluster_scope, seqno);
> > > +       /* tipc_net_finalize_work() has not finished inserting self address to
> > > +         * name table yet.
> > > +         */
> > > +       if (unlikely(list_empty(&nt->cluster_scope))) {
> > > +               read_unlock_bh(&nt->cluster_scope_lock);
> > > +               return 1;
> > > +       }
> > > +
> > > +       if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
> > > +               read_unlock_bh(&nt->cluster_scope_lock);
> > > +               return -ENOBUFS;
> > > +       }
> > > +
> > >         tipc_node_xmit(net, &head, dnode, 0);
> > >         read_unlock_bh(&nt->cluster_scope_lock);
> > > +       return 0;
> > > +}
> > > +
> > > +int tipc_named_dist_cluster_scope(struct net *net, u32 dnode)
> > > +{
> > > +       struct name_table *nt = tipc_name_table(net);
> > > +       struct tipc_net *tn = tipc_net(net);
> > > +       struct sk_buff_head head;
> > > +       u16 seqno;
> > > +
> > > +       __skb_queue_head_init(&head);
> > > +       wait_var_event(&tn->finalized, atomic_read(&tn->finalized));
> > > +       spin_lock_bh(&tn->nametbl_lock);
> > > +       seqno = nt->snd_nxt;
> > > +       spin_unlock_bh(&tn->nametbl_lock);
> > > +
> > > +       read_lock_bh(&nt->cluster_scope_lock);
> > > +       if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
> > > +               read_unlock_bh(&nt->cluster_scope_lock);
> > > +               return -ENOBUFS;
> > > +       }
> > > +       tipc_node_xmit(net, &head, dnode, 0);
> > > +       read_unlock_bh(&nt->cluster_scope_lock);
> > > +
> > > +       return 0;
> > >  }
> > >
> > >  /**
> > > diff --git a/net/tipc/name_distr.h b/net/tipc/name_distr.h
> > > index c677f6f082df..cadf4e8c3e66 100644
> > > --- a/net/tipc/name_distr.h
> > > +++ b/net/tipc/name_distr.h
> > > @@ -69,7 +69,8 @@ struct distr_item {
> > >
> > >  struct sk_buff *tipc_named_publish(struct net *net, struct publication *publ);
> > >  struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *publ);
> > > -void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
> > > +int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
> > > +int tipc_named_dist_cluster_scope(struct net *net, u32 dnode);
> > >  void tipc_named_rcv(struct net *net, struct sk_buff_head *namedq,
> > >                     u16 *rcv_nxt, bool *open);
> > >  void tipc_named_reinit(struct net *net);
> > > diff --git a/net/tipc/net.c b/net/tipc/net.c
> > > index 7e65d0b0c4a8..4c144e720ac1 100644
> > > --- a/net/tipc/net.c
> > > +++ b/net/tipc/net.c
> > > @@ -139,6 +139,8 @@ static void tipc_net_finalize(struct net *net, u32 addr)
> > >         tipc_sk_reinit(net);
> > >         tipc_mon_reinit_self(net);
> > >         tipc_nametbl_publish(net, &ua, &sk, addr);
> > > +       atomic_inc(&tn->finalized);
> > > +       wake_up_var(&tn->finalized);
> > >  }
> > >
> > >  void tipc_net_finalize_work(struct work_struct *work)
> > > diff --git a/net/tipc/node.c b/net/tipc/node.c
> > > index 8e4ef2630ae4..c5b0a98324c3 100644
> > > --- a/net/tipc/node.c
> > > +++ b/net/tipc/node.c
> > > @@ -145,6 +145,8 @@ struct tipc_node {
> > >  #ifdef CONFIG_TIPC_CRYPTO
> > >         struct tipc_crypto *crypto_rx;
> > >  #endif
> > > +       /* Work item for bulk distribution of cluster scope publications */
> > > +       struct work_struct work;
> > >  };
> > >
> > >  /* Node FSM states and events:
> > > @@ -303,6 +305,7 @@ static void tipc_node_free(struct rcu_head *rp)
> > >  #ifdef CONFIG_TIPC_CRYPTO
> > >         tipc_crypto_stop(&n->crypto_rx);
> > >  #endif
> > > +       cancel_work_sync(&n->work);
> > >         kfree(n);
> > >  }
> > >
> > > @@ -393,6 +396,19 @@ static void tipc_node_write_unlock_fast(struct tipc_node *n)
> > >         write_unlock_bh(&n->lock);
> > >  }
> > >
> > > +static void tipc_node_dist_bulk(struct work_struct *work)
> > > +{
> > > +       struct tipc_node *node = container_of(work, struct tipc_node, work);
> > > +
> > > +       if (tipc_named_dist_cluster_scope(node->net, node->addr) < 0) {
> > > +               u32 bearer_id = node->link_id & 0xffff;
> > > +
> > > +               tipc_node_link_down(node, bearer_id, false);
> > > +       }
> > > +
> > > +       tipc_node_put(node);
> > > +}
> > > +
> > > static void tipc_node_write_unlock(struct tipc_node *n)
> > >         __releases(n->lock)
> > >  {
> > > @@ -424,8 +440,21 @@ static void tipc_node_write_unlock(struct tipc_node *n)
> > >         if (flags & TIPC_NOTIFY_NODE_DOWN)
> > >                 tipc_publ_notify(net, publ_list, node, n->capabilities);
> > >
> > > -       if (flags & TIPC_NOTIFY_NODE_UP)
> > > -               tipc_named_node_up(net, node, n->capabilities);
> > > +       if (flags & TIPC_NOTIFY_NODE_UP) {
> > > +               int rc = 0;
> > > +
> > > +               rc = tipc_named_node_up(net, node, n->capabilities);
> > > +               /* Defer bulk distribution to work queue */
> > > +               if (rc > 0) {
> > > +                       tipc_node_get(n);
> > > +                       schedule_work(&n->work);
> > > +               } else if (rc < 0) {
> > > +                       /* Bring the link down to start over bulk distribution
> > > +                        * when the link is up again.
> > > +                        */
> > > +                       tipc_node_link_down(n, bearer_id, false);
> > > +               }
> > > +       }
> > >
> > >         if (flags & TIPC_NOTIFY_LINK_UP) {
> > >                 tipc_mon_peer_up(net, node, bearer_id);
> > > @@ -564,6 +593,7 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
> > >         INIT_LIST_HEAD(&n->list);
> > >         INIT_LIST_HEAD(&n->publ_list);
> > >         INIT_LIST_HEAD(&n->conn_sks);
> > > +       INIT_WORK(&n->work, tipc_node_dist_bulk);
> > >         skb_queue_head_init(&n->bc_entry.namedq);
> > >         skb_queue_head_init(&n->bc_entry.inputq1);
> > >         __skb_queue_head_init(&n->bc_entry.arrvq);

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox