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&amp;#39;t get added<br /> as fragments in ice_add_xdp_frag. Since the buffer isn&amp;#39;t counted as a<br /> fragment, we do not iterate over it in ice_put_rx_mbuf(), and thus we don&amp;#39;t<br /> call ice_put_rx_buf().<br /> <br /> Because we don&amp;#39;t call ice_put_rx_buf(), we don&amp;#39;t attempt to re-use the<br /> page or free it. This leaves a stale page in the ring, as we don&amp;#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&amp;#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.

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:*:*:*:*:*:*