net: stmmac: Prevent NULL deref when RX memory exhausted
Subsystems (5)
PATCH v6 1/1 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.
---
This is v6 of [1], which was itself split out of [2]. This patch prevents a NULL dereference in the stmmac receive path, and (at Russell's suggestion) in the zero-copy path as well.
The approach is different from the previous version and checks the dirty_rx index in the loop proper, copied directly from Russell's suggestion [3]. Parts of the commit message also use his phrasing. For these reasons he is credited with Suggested-by.
The commit message now acknowledges the pipeline stall that can occur in case of failure of the next stmmac_rx_refill() after the MAC consumes the final descriptor. I still intend to fix that bug when I can find the time to finish investigating and implement the timer as requested by Jakub, however I'm sending this patch now to resolve the outright _panic_ and simplify review. The stmmac_rx_zc() path is not affected by this stall.
[1] https://lore.kernel.org/netdev/[email protected]/
[2] https://lore.kernel.org/netdev/[email protected]/
[3] https://lore.kernel.org/netdev/[email protected]/
--- .../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
@@ -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);
Signature · 1 line
2.52.0
Paolo Abeni
maintainerQuoted 89 lines · Sam Edwards
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 thecur_rxindex. 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 incrementscur_rxmodulo the ring size. After the loop exits,stmmac_rx_refill(), which bookmarks its position withdirty_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 confusesfull/dirty. In the past (see 'Fixes:'), there was a bug where the loop could cyclecur_rxall the way back to the first descriptor it dirtied, resulting in a NULL dereference when mistaken forfull. The aforementioned commit resolved that *specific* failure by capping the loop's iteration limit atdma_rx_size - 1, but this is only a partial fix: if the previousstmmac_rx_refill()didn't complete, then there are leftoverdirtydescriptors that the loop might encounter without needing to cycle fully around. The current code therefore panics (see 'Closes:') whenstmmac_rx_refill()is memory-starved long enough forcur_rxto catch up todirty_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 untilstmmac_rx_refill()succeeds, but fully prevents thecur_rx == dirty_rxambiguity as the previous bugfix
intended: so remove the clamp as well. Sincestmmac_rx_zc()is a copy-paste-and-tweak ofstmmac_rx()and the code structure is identical, any fix tostmmac_rx()will also need a corresponding fix forstmmac_rx_zc(). Therefore, apply the same check there.In
stmmac_rx()(notstmmac_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 moreemptydescriptors. Currently, the driver simply *hopes* that the nextstmmac_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.Fixes b6cb4541853c7 ("net: stmmac: avoid rx queue overrun")Suggested-by Russell King <[email protected]>Signed-off-by Sam Edwards <[email protected]>---
This is v6 of [1], which was itself split out of [2]. This patch prevents a NULL dereference in the stmmac receive path, and (at Russell's suggestion) in the zero-copy path as well.
The approach is different from the previous version and checks the
dirty_rxindex in the loop proper, copied directly from Russell's suggestion [3]. Parts of the commit message also use his phrasing. For these reasons he is credited withSuggested-by.The commit message now acknowledges the pipeline stall that can occur in case of failure of the next
stmmac_rx_refill()after the MAC consumes the final descriptor. I still intend to fix that bug when I can find the time to finish investigating and implement the timer as requested by Jakub, however I'm sending this patch now to resolve the outright _panic_ and simplify review. Thestmmac_rx_zc()path is not affected by this stall.[1] https://lore.kernel.org/netdev/[email protected]/
[2] https://lore.kernel.org/netdev/[email protected]/
[3] https://lore.kernel.org/netdev/[email protected]/--- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-)drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +0
-0
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue) · line 5555
Quoted 10 lines
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +4
-3@@ -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 ;
Sashiko notes that breaking the loop of DMA descriptors owned by the CPU may cause double accounting for the ingress stats by stmmac_rx_status().
AFAICS that is not a regression, as the existing later XDP check already does the same, so I think that problem should be addressed separately.
/P
This patch was applied to netdev/net.git (main) by Paolo Abeni <[email protected]>:
Hello:
This patch was applied to netdev/net.git (main)
by Paolo Abeni <[email protected]>:
Quoted 11 lines · On Tue, 21 Apr 2026 21:45:03 -0700 you wrote:
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."
Quoted 1 line
[...]
Here is the summary with links:
- [net,v6] net: stmmac: Prevent NULL deref when RX memory exhausted
https://git.kernel.org/netdev/net/c/0bb05e6adfa9
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html