CVE-2025-39948
Severity CVSS v4.0:
Pending analysis
Type:
Unavailable / Other
Publication date:
04/10/2025
Last modified:
27/01/2026
Description
In the Linux kernel, the following vulnerability has been resolved:<br />
<br />
ice: fix Rx page leak on multi-buffer frames<br />
<br />
The ice_put_rx_mbuf() function handles calling ice_put_rx_buf() for each<br />
buffer in the current frame. This function was introduced as part of<br />
handling multi-buffer XDP support in the ice driver.<br />
<br />
It works by iterating over the buffers from first_desc up to 1 plus the<br />
total number of fragments in the frame, cached from before the XDP program<br />
was executed.<br />
<br />
If the hardware posts a descriptor with a size of 0, the logic used in<br />
ice_put_rx_mbuf() breaks. Such descriptors get skipped and don&#39;t get added<br />
as fragments in ice_add_xdp_frag. Since the buffer isn&#39;t counted as a<br />
fragment, we do not iterate over it in ice_put_rx_mbuf(), and thus we don&#39;t<br />
call ice_put_rx_buf().<br />
<br />
Because we don&#39;t call ice_put_rx_buf(), we don&#39;t attempt to re-use the<br />
page or free it. This leaves a stale page in the ring, as we don&#39;t<br />
increment next_to_alloc.<br />
<br />
The ice_reuse_rx_page() assumes that the next_to_alloc has been incremented<br />
properly, and that it always points to a buffer with a NULL page. Since<br />
this function doesn&#39;t check, it will happily recycle a page over the top<br />
of the next_to_alloc buffer, losing track of the old page.<br />
<br />
Note that this leak only occurs for multi-buffer frames. The<br />
ice_put_rx_mbuf() function always handles at least one buffer, so a<br />
single-buffer frame will always get handled correctly. It is not clear<br />
precisely why the hardware hands us descriptors with a size of 0 sometimes,<br />
but it happens somewhat regularly with "jumbo frames" used by 9K MTU.<br />
<br />
To fix ice_put_rx_mbuf(), we need to make sure to call ice_put_rx_buf() on<br />
all buffers between first_desc and next_to_clean. Borrow the logic of a<br />
similar function in i40e used for this same purpose. Use the same logic<br />
also in ice_get_pgcnts().<br />
<br />
Instead of iterating over just the number of fragments, use a loop which<br />
iterates until the current index reaches to the next_to_clean element just<br />
past the current frame. Unlike i40e, the ice_put_rx_mbuf() function does<br />
call ice_put_rx_buf() on the last buffer of the frame indicating the end of<br />
packet.<br />
<br />
For non-linear (multi-buffer) frames, we need to take care when adjusting<br />
the pagecnt_bias. An XDP program might release fragments from the tail of<br />
the frame, in which case that fragment page is already released. Only<br />
update the pagecnt_bias for the first descriptor and fragments still<br />
remaining post-XDP program. Take care to only access the shared info for<br />
fragmented buffers, as this avoids a significant cache miss.<br />
<br />
The xdp_xmit value only needs to be updated if an XDP program is run, and<br />
only once per packet. Drop the xdp_xmit pointer argument from<br />
ice_put_rx_mbuf(). Instead, set xdp_xmit in the ice_clean_rx_irq() function<br />
directly. This avoids needing to pass the argument and avoids an extra<br />
bit-wise OR for each buffer in the frame.<br />
<br />
Move the increment of the ntc local variable to ensure its updated *before*<br />
all calls to ice_get_pgcnts() or ice_put_rx_mbuf(), as the loop logic<br />
requires the index of the element just after the current frame.<br />
<br />
Now that we use an index pointer in the ring to identify the packet, we no<br />
longer need to track or cache the number of fragments in the rx_ring.
Impact
Base Score 3.x
5.50
Severity 3.x
MEDIUM
Vulnerable products and versions
| CPE | From | Up to |
|---|---|---|
| cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* | 6.6.78 (including) | 6.7 (excluding) |
| cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* | 6.12.14 (including) | 6.12.49 (excluding) |
| cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* | 6.13.3 (including) | 6.14 (excluding) |
| cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* | 6.14.1 (including) | 6.16.9 (excluding) |
| cpe:2.3:o:linux:linux_kernel:6.14:-:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.14:rc2:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.14:rc3:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.14:rc4:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.14:rc5:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.14:rc6:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.14:rc7:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.17:rc1:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.17:rc2:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.17:rc3:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:6.17:rc4:*:*:*:*:*:* |
To consult the complete list of CPE names with products and versions, see this page



