FAILED: patch "[PATCH] net: stmmac: Prevent NULL deref when RX memory exhausted" failed to apply to 6.12-stable tree
Subsystems (4)
[email protected]
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 0bb05e6adfa99a2ea1fee1125cc0953409f83ed8
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026050448-gawk-hybrid-73b9@gregkh' --subject-prefix 'PATCH 6.12.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 0bb05e6adfa99a2ea1fee1125cc0953409f83ed8 Mon Sep 17 00:00:00 2001
From: Sam Edwards <[email protected]>
Date: Tue, 21 Apr 2026 21:45:03 -0700
Subject: [PATCH] net: stmmac: Prevent NULL deref when RX memory exhausted
The CPU receives frames from the MAC through conventional DMA: the CPU allocates buffers for the MAC, then the MAC fills them and returns ownership to the CPU. For each hardware RX queue, the CPU and MAC coordinate through a shared ring array of DMA descriptors: one descriptor per DMA buffer. Each descriptor includes the buffer's physical address and a status flag ("OWN") indicating which side owns the buffer: OWN=0 for CPU, OWN=1 for MAC. The CPU is only allowed to set the flag and the MAC is only allowed to clear it, and both must move through the ring in sequence: thus the ring is used for both "submissions" and "completions."
In the stmmac driver, stmmac_rx() bookmarks its position in the ring with the cur_rx index. The main receive loop in that function checks for rx_descs[cur_rx].own=0, gives the corresponding buffer to the network stack (NULLing the pointer), and increments cur_rx modulo the ring size. After the loop exits, stmmac_rx_refill(), which bookmarks its position with dirty_rx, allocates fresh buffers and rearms the descriptors (setting OWN=1). If it fails any allocation, it simply stops early (leaving OWN=0) and will retry where it left off when next called.
This means descriptors have a three-stage lifecycle (terms my own):
- empty (OWN=1, buffer valid)
- full (OWN=0, buffer valid and populated)
- dirty (OWN=0, buffer NULL)
But because stmmac_rx() only checks OWN, it confuses full/dirty. In the past (see 'Fixes:'), there was a bug where the loop could cycle cur_rx all the way back to the first descriptor it dirtied, resulting in a NULL dereference when mistaken for full. The aforementioned commit resolved that *specific* failure by capping the loop's iteration limit at dma_rx_size - 1, but this is only a partial fix: if the previous stmmac_rx_refill() didn't complete, then there are leftover dirty descriptors that the loop might encounter without needing to cycle fully around. The current code therefore panics (see 'Closes:') when stmmac_rx_refill() is memory-starved long enough for cur_rx to catch up to dirty_rx.
Fix this by explicitly checking, before advancing cur_rx, if the next entry is dirty; exit the loop if so. This prevents processing of the final, used descriptor until stmmac_rx_refill() succeeds, but fully prevents the cur_rx == dirty_rx ambiguity as the previous bugfix
intended: so remove the clamp as well. Since stmmac_rx_zc() is a copy-paste-and-tweak of stmmac_rx() and the code structure is identical, any fix to stmmac_rx() will also need a corresponding fix for stmmac_rx_zc(). Therefore, apply the same check there.
In stmmac_rx() (not stmmac_rx_zc()), a related bug remains: after the MAC sets OWN=0 on the final descriptor, it will be unable to send any further DMA-complete IRQs until it's given more empty descriptors. Currently, the driver simply *hopes* that the next stmmac_rx_refill() succeeds, risking an indefinite stall of the receive process if not. But this is not a regression, so it can be addressed in a future change.
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +12 -7
@@ -5549,9 +5549,12 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)break ;/* Prefetch the next RX descriptor */ - rx_q->cur_rx =STMMAC_NEXT_ENTRY (rx_q->cur_rx, - priv->dma_conf.dma_rx_size); - next_entry = rx_q->cur_rx; + next_entry =STMMAC_NEXT_ENTRY (rx_q->cur_rx, + priv->dma_conf.dma_rx_size); +if (unlikely (next_entry == rx_q->dirty_rx)) +break ; + + rx_q->cur_rx = next_entry; np =stmmac_get_rx_desc (priv, rx_q, next_entry);
@@ -5686,7 +5689,6 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) dma_dir =page_pool_get_dma_dir (rx_q->page_pool); bufsz =DIV_ROUND_UP (priv->dma_conf.dma_buf_sz,PAGE_SIZE ) *PAGE_SIZE ; - limit =min (priv->dma_conf.dma_rx_size -1 , (unsigned int )limit);if (netif_msg_rx_status (priv)) {void *rx_head =stmmac_get_rx_desc (priv, rx_q,0 );
@@ -5733,9 +5735,12 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)if (unlikely (status & dma_own))break ; - rx_q->cur_rx =STMMAC_NEXT_ENTRY (rx_q->cur_rx, - priv->dma_conf.dma_rx_size); - next_entry = rx_q->cur_rx; + next_entry =STMMAC_NEXT_ENTRY (rx_q->cur_rx, + priv->dma_conf.dma_rx_size); +if (unlikely (next_entry == rx_q->dirty_rx)) +break ; + + rx_q->cur_rx = next_entry; np =stmmac_get_rx_desc (priv, rx_q, next_entry);
Sasha Levin
maintainerPATCH 1/3 net: stmmac: avoid shadowing global buf_sz
From: "Russell King (Oracle)" <[email protected]>
[ Upstream commit 876cfb20e8892143c0c967b3657074f9131f9b5f ]
stmmac_rx() declares a local variable named "buf_sz" but there is also a global variable for a module parameter which is called the same. To avoid confusion, rename the local variable.
Stable-dep-of: 0bb05e6adfa9 ("net: stmmac: Prevent NULL deref when RX memory exhausted")
--- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +3 -3
@@ -5406,10 +5406,10 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)struct sk_buff *skb =NULL ;struct stmmac_xdp_buff ctx;int xdp_status =0 ; -int buf_sz; +int bufsz; dma_dir =page_pool_get_dma_dir (rx_q->page_pool); - buf_sz =DIV_ROUND_UP (priv->dma_conf.dma_buf_sz,PAGE_SIZE ) *PAGE_SIZE ; + bufsz =DIV_ROUND_UP (priv->dma_conf.dma_buf_sz,PAGE_SIZE ) *PAGE_SIZE ; limit =min (priv->dma_conf.dma_rx_size -1 , (unsigned int )limit);if (netif_msg_rx_status (priv)) {
@@ -5524,7 +5524,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)dma_sync_single_for_cpu (priv->device, buf->addr, buf1_len, dma_dir); -xdp_init_buff (&ctx.xdp, buf_sz, &rx_q->xdp_rxq); +xdp_init_buff (&ctx.xdp, bufsz, &rx_q->xdp_rxq);xdp_prepare_buff (&ctx.xdp,page_address (buf->page), buf->page_offset, buf1_len, true);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/3 net: stmmac: rename STMMAC_GET_ENTRY() -> STMMAC_NEXT_ENTRY()
From: "Russell King (Oracle)" <[email protected]>
[ Upstream commit 6b4286e0550814cdc4b897f881ec1fa8b0313227 ]
STMMAC_GET_ENTRY() doesn't describe what this macro is doing - it is incrementing the provided index for the circular array of descriptors. Replace "GET" with "NEXT" as this better describes the action here.
Stable-dep-of: 0bb05e6adfa9 ("net: stmmac: Prevent NULL deref when RX memory exhausted")
--- .../net/ethernet/stmicro/stmmac/chain_mode.c | 2 +- drivers/net/ethernet/stmicro/stmmac/common.h | 2 +- .../net/ethernet/stmicro/stmmac/ring_mode.c | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 26 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-)
drivers/net/ethernet/stmicro/stmmac/chain_mode.c +1 -1
@@ -47,7 +47,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,while (len !=0 ) { tx_q->tx_skbuff[entry] =NULL ; - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_tx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_tx_size); desc = tx_q->dma_tx + entry;if (len > bmax) {
drivers/net/ethernet/stmicro/stmmac/common.h +1 -1
@@ -55,7 +55,7 @@#define DMA_MIN_RX_SIZE 64#define DMA_MAX_RX_SIZE 1024#define DMA_DEFAULT_RX_SIZE 512 -#define STMMAC_GET_ENTRY (x, size) ((x + 1) & (size - 1)) +#define STMMAC_NEXT_ENTRY (x, size) ((x + 1) & (size - 1))#undef FRAME_FILTER_DEBUG/* #define FRAME_FILTER_DEBUG */
drivers/net/ethernet/stmicro/stmmac/ring_mode.c +1 -1
@@ -51,7 +51,7 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,stmmac_prepare_tx_desc (priv, desc,1 , bmax, csum,STMMAC_RING_MODE ,0 , false, skb->len); tx_q->tx_skbuff[entry] =NULL ; - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_tx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_tx_size);if (priv->extend_desc) desc = (struct dma_desc *)(tx_q->dma_etx + entry);
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +13 -13
@@ -2583,7 +2583,7 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)xsk_tx_metadata_to_compl (meta, &tx_q->tx_skbuff_dma[entry].xsk_meta); - tx_q->cur_tx =STMMAC_GET_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size); + tx_q->cur_tx =STMMAC_NEXT_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size); entry = tx_q->cur_tx; }u64_stats_update_begin (&txq_stats->napi_syncp);
@@ -2754,7 +2754,7 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,stmmac_release_tx_desc (priv, p, priv->mode); - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_tx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_tx_size); } tx_q->dirty_tx = entry;
@@ -4076,7 +4076,7 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb,return false;stmmac_set_tx_owner (priv, p); - tx_q->cur_tx =STMMAC_GET_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size); + tx_q->cur_tx =STMMAC_NEXT_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size);return true; }
@@ -4104,7 +4104,7 @@ static void stmmac_tso_allocator(struct stmmac_priv *priv, dma_addr_t des,while (tmp_len >0 ) {dma_addr_t curr_addr; - tx_q->cur_tx =STMMAC_GET_ENTRY (tx_q->cur_tx, + tx_q->cur_tx =STMMAC_NEXT_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size);WARN_ON (tx_q->tx_skbuff[tx_q->cur_tx]);
@@ -4250,7 +4250,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)stmmac_set_mss (priv, mss_desc, mss); tx_q->mss = mss; - tx_q->cur_tx =STMMAC_GET_ENTRY (tx_q->cur_tx, + tx_q->cur_tx =STMMAC_NEXT_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size);WARN_ON (tx_q->tx_skbuff[tx_q->cur_tx]); }
@@ -4369,7 +4369,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)* ndo_start_xmit will fill this descriptor the next time it's * called and stmmac_tx_clean may clean up to this descriptor. */ - tx_q->cur_tx =STMMAC_GET_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size); + tx_q->cur_tx =STMMAC_NEXT_ENTRY (tx_q->cur_tx, priv->dma_conf.dma_tx_size);if (unlikely (stmmac_tx_avail (priv, queue) <= (MAX_SKB_FRAGS +1 ))) { netif_dbg(priv, hw, priv->dev, "%s: stop transmitted packets\n",
@@ -4568,7 +4568,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)int len =skb_frag_size (frag);bool last_segment = (i == (nfrags -1 )); - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_tx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_tx_size);WARN_ON (tx_q->tx_skbuff[entry]);if (likely (priv->extend_desc))
@@ -4638,7 +4638,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)* ndo_start_xmit will fill this descriptor the next time it's * called and stmmac_tx_clean may clean up to this descriptor. */ - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_tx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_tx_size); tx_q->cur_tx = entry;if (netif_msg_pktdata (priv)) {
@@ -4807,7 +4807,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)dma_wmb ();stmmac_set_rx_owner (priv, p, use_rx_wd); - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_rx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_rx_size); } rx_q->dirty_rx = entry; rx_q->rx_tail_addr = rx_q->dma_rx_phy +
@@ -4941,7 +4941,7 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,stmmac_enable_dma_transmission (priv, priv->ioaddr, queue); - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_tx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_tx_size); tx_q->cur_tx = entry;return STMMAC_XDP_TX ;
@@ -5175,7 +5175,7 @@ static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget)dma_wmb ();stmmac_set_rx_owner (priv, rx_desc, use_rx_wd); - entry =STMMAC_GET_ENTRY (entry, priv->dma_conf.dma_rx_size); + entry =STMMAC_NEXT_ENTRY (entry, priv->dma_conf.dma_rx_size); }if (rx_desc) {
@@ -5270,7 +5270,7 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)break ;/* Prefetch the next RX descriptor */ - rx_q->cur_rx =STMMAC_GET_ENTRY (rx_q->cur_rx, + rx_q->cur_rx =STMMAC_NEXT_ENTRY (rx_q->cur_rx, priv->dma_conf.dma_rx_size); next_entry = rx_q->cur_rx;
@@ -5466,7 +5466,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)if (unlikely (status & dma_own))break ; - rx_q->cur_rx =STMMAC_GET_ENTRY (rx_q->cur_rx, + rx_q->cur_rx =STMMAC_NEXT_ENTRY (rx_q->cur_rx, priv->dma_conf.dma_rx_size); next_entry = rx_q->cur_rx;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/3 net: stmmac: Prevent NULL deref when RX memory exhausted
In reply to Sasha LevinFrom: Sam Edwards <[email protected]>
[ Upstream commit 0bb05e6adfa99a2ea1fee1125cc0953409f83ed8 ]
The CPU receives frames from the MAC through conventional DMA: the CPU allocates buffers for the MAC, then the MAC fills them and returns ownership to the CPU. For each hardware RX queue, the CPU and MAC coordinate through a shared ring array of DMA descriptors: one descriptor per DMA buffer. Each descriptor includes the buffer's physical address and a status flag ("OWN") indicating which side owns the buffer: OWN=0 for CPU, OWN=1 for MAC. The CPU is only allowed to set the flag and the MAC is only allowed to clear it, and both must move through the ring in sequence: thus the ring is used for both "submissions" and "completions."
In the stmmac driver, stmmac_rx() bookmarks its position in the ring with the cur_rx index. The main receive loop in that function checks for rx_descs[cur_rx].own=0, gives the corresponding buffer to the network stack (NULLing the pointer), and increments cur_rx modulo the ring size. After the loop exits, stmmac_rx_refill(), which bookmarks its position with dirty_rx, allocates fresh buffers and rearms the descriptors (setting OWN=1). If it fails any allocation, it simply stops early (leaving OWN=0) and will retry where it left off when next called.
This means descriptors have a three-stage lifecycle (terms my own):
- empty (OWN=1, buffer valid)
- full (OWN=0, buffer valid and populated)
- dirty (OWN=0, buffer NULL)
But because stmmac_rx() only checks OWN, it confuses full/dirty. In the past (see 'Fixes:'), there was a bug where the loop could cycle cur_rx all the way back to the first descriptor it dirtied, resulting in a NULL dereference when mistaken for full. The aforementioned commit resolved that *specific* failure by capping the loop's iteration limit at dma_rx_size - 1, but this is only a partial fix: if the previous stmmac_rx_refill() didn't complete, then there are leftover dirty descriptors that the loop might encounter without needing to cycle fully around. The current code therefore panics (see 'Closes:') when stmmac_rx_refill() is memory-starved long enough for cur_rx to catch up to dirty_rx.
Fix this by explicitly checking, before advancing cur_rx, if the next entry is dirty; exit the loop if so. This prevents processing of the final, used descriptor until stmmac_rx_refill() succeeds, but fully prevents the cur_rx == dirty_rx ambiguity as the previous bugfix
intended: so remove the clamp as well. Since stmmac_rx_zc() is a copy-paste-and-tweak of stmmac_rx() and the code structure is identical, any fix to stmmac_rx() will also need a corresponding fix for stmmac_rx_zc(). Therefore, apply the same check there.
In stmmac_rx() (not stmmac_rx_zc()), a related bug remains: after the MAC sets OWN=0 on the final descriptor, it will be unable to send any further DMA-complete IRQs until it's given more empty descriptors. Currently, the driver simply *hopes* that the next stmmac_rx_refill() succeeds, risking an indefinite stall of the receive process if not. But this is not a regression, so it can be addressed in a future change.
--- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-)
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +12 -7
@@ -5270,9 +5270,12 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)break ;/* Prefetch the next RX descriptor */ - rx_q->cur_rx =STMMAC_NEXT_ENTRY (rx_q->cur_rx, - priv->dma_conf.dma_rx_size); - next_entry = rx_q->cur_rx; + next_entry =STMMAC_NEXT_ENTRY (rx_q->cur_rx, + priv->dma_conf.dma_rx_size); +if (unlikely (next_entry == rx_q->dirty_rx)) +break ; + + rx_q->cur_rx = next_entry;if (priv->extend_desc) np = (struct dma_desc *)(rx_q->dma_erx + next_entry);
@@ -5410,7 +5413,6 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) dma_dir =page_pool_get_dma_dir (rx_q->page_pool); bufsz =DIV_ROUND_UP (priv->dma_conf.dma_buf_sz,PAGE_SIZE ) *PAGE_SIZE ; - limit =min (priv->dma_conf.dma_rx_size -1 , (unsigned int )limit);if (netif_msg_rx_status (priv)) {void *rx_head;
@@ -5466,9 +5468,12 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)if (unlikely (status & dma_own))break ; - rx_q->cur_rx =STMMAC_NEXT_ENTRY (rx_q->cur_rx, - priv->dma_conf.dma_rx_size); - next_entry = rx_q->cur_rx; + next_entry =STMMAC_NEXT_ENTRY (rx_q->cur_rx, + priv->dma_conf.dma_rx_size); +if (unlikely (next_entry == rx_q->dirty_rx)) +break ; + + rx_q->cur_rx = next_entry;if (priv->extend_desc) np = (struct dma_desc *)(rx_q->dma_erx + next_entry);
Signature · 1 line
2.53.0