- Description
- In the Linux kernel, the following vulnerability has been resolved:
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.
- Source
- 416baaa9-dc9f-4396-8d5f-8c081fb06d67
- NVD status
- Analyzed
- Products
- linux_kernel
[
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
"matchCriteriaId": "7D2385BE-AA5B-4108-986E-94FE78869E0B",
"versionEndExcluding": "6.1.176",
"versionStartIncluding": "6.1.64",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
"matchCriteriaId": "3B6AC16B-2F1C-4FDF-8F49-09FC11C3F5B0",
"versionEndExcluding": "6.6",
"versionStartIncluding": "6.5.13",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
"matchCriteriaId": "5BEAFABA-7E5B-445F-93D8-C490B82429EC",
"versionEndExcluding": "6.6.140",
"versionStartIncluding": "6.6.3",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
"matchCriteriaId": "269B1B34-9934-4742-8224-E11B647B979B",
"versionEndExcluding": "6.12.88",
"versionStartIncluding": "6.7.1",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
"matchCriteriaId": "BF39AE08-AE6D-4410-8FBE-76F6BF5BF55B",
"versionEndExcluding": "6.18.30",
"versionStartIncluding": "6.13",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
"matchCriteriaId": "D0893CA7-9AE6-4DFE-AC75-48967D73AD8E",
"versionEndExcluding": "7.0.7",
"versionStartIncluding": "6.19",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:-:*:*:*:*:*:*",
"matchCriteriaId": "62B55B1B-7D3E-499B-9C42-E9F1EF05A54A",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:rc2:*:*:*:*:*:*",
"matchCriteriaId": "E31AD4FC-436C-44AB-BCAB-3A0B37F69EE0",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:rc3:*:*:*:*:*:*",
"matchCriteriaId": "C56C6E04-4F04-44A3-8DB8-93899903CFCF",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:rc4:*:*:*:*:*:*",
"matchCriteriaId": "5C78EDA4-8BE6-42FC-9512-49032D525A55",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:rc5:*:*:*:*:*:*",
"matchCriteriaId": "32F2E5CA-13C6-4601-B530-D465CBF73D1C",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:rc6:*:*:*:*:*:*",
"matchCriteriaId": "5ED5AF93-F831-48BC-9545-CCB344E814FC",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:rc7:*:*:*:*:*:*",
"matchCriteriaId": "81A7ABCB-0807-4AA2-8F4E-75E38D2E3FD4",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:6.7:rc8:*:*:*:*:*:*",
"matchCriteriaId": "B01471D6-2DB4-4AF2-8BE0-B5082B4B9253",
"vulnerable": true
},
{
"criteria": "cpe:2.3:o:linux:linux_kernel:7.1:rc1:*:*:*:*:*:*",
"matchCriteriaId": "B1EF7059-E670-45F4-B422-54C40FA86390",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
]
}
]