{ "dataType": "CVE_RECORD", "dataVersion": "5.2", "cveMetadata": { "cveId": "CVE-2025-39948", "assignerOrgId": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "state": "PUBLISHED", "assignerShortName": "Linux", "dateReserved": "2025-04-16T07:20:57.148Z", "datePublished": "2025-10-04T07:31:09.403Z", "dateUpdated": "2026-08-05T12:06:22.439Z" }, "containers": { "cna": { "providerMetadata": { "orgId": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "shortName": "Linux", "dateUpdated": "2026-08-05T12:06:22.439Z" }, "descriptions": [ { "lang": "en", "value": "In the Linux kernel, the following vulnerability has been resolved:\n\nice: fix Rx page leak on multi-buffer frames\n\nThe ice_put_rx_mbuf() function handles calling ice_put_rx_buf() for each\nbuffer in the current frame. This function was introduced as part of\nhandling multi-buffer XDP support in the ice driver.\n\nIt works by iterating over the buffers from first_desc up to 1 plus the\ntotal number of fragments in the frame, cached from before the XDP program\nwas executed.\n\nIf the hardware posts a descriptor with a size of 0, the logic used in\nice_put_rx_mbuf() breaks. Such descriptors get skipped and don't get added\nas fragments in ice_add_xdp_frag. Since the buffer isn't counted as a\nfragment, we do not iterate over it in ice_put_rx_mbuf(), and thus we don't\ncall ice_put_rx_buf().\n\nBecause we don't call ice_put_rx_buf(), we don't attempt to re-use the\npage or free it. This leaves a stale page in the ring, as we don't\nincrement next_to_alloc.\n\nThe ice_reuse_rx_page() assumes that the next_to_alloc has been incremented\nproperly, and that it always points to a buffer with a NULL page. Since\nthis function doesn't check, it will happily recycle a page over the top\nof the next_to_alloc buffer, losing track of the old page.\n\nNote that this leak only occurs for multi-buffer frames. The\nice_put_rx_mbuf() function always handles at least one buffer, so a\nsingle-buffer frame will always get handled correctly. It is not clear\nprecisely why the hardware hands us descriptors with a size of 0 sometimes,\nbut it happens somewhat regularly with \"jumbo frames\" used by 9K MTU.\n\nTo fix ice_put_rx_mbuf(), we need to make sure to call ice_put_rx_buf() on\nall buffers between first_desc and next_to_clean. Borrow the logic of a\nsimilar function in i40e used for this same purpose. Use the same logic\nalso in ice_get_pgcnts().\n\nInstead of iterating over just the number of fragments, use a loop which\niterates until the current index reaches to the next_to_clean element just\npast the current frame. Unlike i40e, the ice_put_rx_mbuf() function does\ncall ice_put_rx_buf() on the last buffer of the frame indicating the end of\npacket.\n\nFor non-linear (multi-buffer) frames, we need to take care when adjusting\nthe pagecnt_bias. An XDP program might release fragments from the tail of\nthe frame, in which case that fragment page is already released. Only\nupdate the pagecnt_bias for the first descriptor and fragments still\nremaining post-XDP program. Take care to only access the shared info for\nfragmented buffers, as this avoids a significant cache miss.\n\nThe xdp_xmit value only needs to be updated if an XDP program is run, and\nonly once per packet. Drop the xdp_xmit pointer argument from\nice_put_rx_mbuf(). Instead, set xdp_xmit in the ice_clean_rx_irq() function\ndirectly. This avoids needing to pass the argument and avoids an extra\nbit-wise OR for each buffer in the frame.\n\nMove the increment of the ntc local variable to ensure its updated *before*\nall calls to ice_get_pgcnts() or ice_put_rx_mbuf(), as the loop logic\nrequires the index of the element just after the current frame.\n\nNow that we use an index pointer in the ring to identify the packet, we no\nlonger need to track or cache the number of fragments in the rx_ring." } ], "metrics": [ { "cvssV3_1": { "version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "baseScore": 9.8, "baseSeverity": "CRITICAL" }, "scenarios": [ { "lang": "en", "value": "AV:N - The vulnerable code is the ice NIC's Rx completion path (`ice_clean_rx_irq`), reached by every inbound Ethernet frame before any protocol demux, socket lookup or firewall filtering, so the attacker only needs to send multi-buffer (>3072 byte) frames at the host. Jumbo MTU is the default on cloud VPC networking (AWS 9001, GCP 8896) and datacenter storage fabrics where such frames are routed across L3, so the trigger is remote, not merely link-local.\nAC:L - The attacker simply floods large frames; Intel documents the zero-size-descriptor condition as occurring \"somewhat regularly\" with 9K MTU, so at volume the leak and the mis-recycled buffer are hit reliably, with no race to win and no memory-layout dependency the attacker cannot influence. Jumbo MTU is a mainstream deployment configuration rather than a rare kernel build option.\nPR:N - Descriptor processing happens in NAPI context before any credential, socket, or netfilter check, so packets to closed ports or packets that would be dropped by the firewall still traverse the buggy code. No account, capability, or session on the target is required.\nUI:N - The bug triggers purely on packet reception by the driver; no local user or administrator has to open, mount, or interact with anything. The victim only needs the interface to be up.\nS:U - The leaked pages, the mis-posted Rx buffers, and the corrupted page refcounts all live in kernel memory managed by the same kernel security authority. No VM, IOMMU, or sandbox boundary is crossed.\nC:H - The unreleased buffer keeps its `page_offset` on the half of the page still owned by an in-flight skb and is re-posted to hardware by `ice_alloc_rx_bufs()`, so a stack-owned buffer — potentially one mapped into userspace by TCP receive zerocopy — is simultaneously used as a DMA target for later packets, aliasing data across unrelated flows and tenants. The `pagecnt_bias` over-increment can additionally free a page still referenced by an skb, giving a use-after-free that exposes freed-page contents.\nI:H - Hardware DMAs attacker-controlled packet data directly over payload of skbs already queued in the network stack, silently corrupting data delivered to applications, and the same buffer is handed to the stack twice. The refcount underflow path frees a live page, yielding a page-level use-after-free that is a classic write primitive via heap reuse.\nA:H - Every occurrence permanently leaks a page together with its DMA/IOVA mapping, and the leak is unbounded and driven purely by remote traffic volume — the reporter observed production hosts steadily losing memory until OOM. The associated buffer aliasing and page refcount underflow also produce corruption and crashes rather than mere degradation." } ] } ], "affected": [ { "product": "Linux", "vendor": "Linux", "defaultStatus": "unaffected", "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git", "programFiles": [ "drivers/net/ethernet/intel/ice/ice_txrx.c", "drivers/net/ethernet/intel/ice/ice_txrx.h" ], "versions": [ { "version": "311813ed013c016d4b0b0985a9ee41f778489077", "lessThan": "80555adb5c892f0e21d243ae96ed997ee520aea9", "status": "affected", "versionType": "git" }, { "version": "743bbd93cf29f653fae0e1416a31f03231689911", "lessThan": "fcb5718ebfe7fd64144e3399280440cce361a3ae", "status": "affected", "versionType": "git" }, { "version": "743bbd93cf29f653fae0e1416a31f03231689911", "lessThan": "84bf1ac85af84d354c7a2fdbdc0d4efc8aaec34b", "status": "affected", "versionType": "git" }, { "version": "ac1728cf370bec2e74fe6a2adf05b4629980d2b3", "status": "affected", "versionType": "git" }, { "version": "d445b59d30415bb56f4803f622d566bca06e0abc", "status": "affected", "versionType": "git" }, { "version": "6.12.14", "lessThan": "6.12.49", "status": "affected", "versionType": "semver" }, { "version": "6.6.78", "lessThan": "6.7", "status": "affected", "versionType": "semver" }, { "version": "6.13.3", "lessThan": "6.14", "status": "affected", "versionType": "semver" } ] }, { "product": "Linux", "vendor": "Linux", "defaultStatus": "affected", "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git", "programFiles": [ "drivers/net/ethernet/intel/ice/ice_txrx.c", "drivers/net/ethernet/intel/ice/ice_txrx.h" ], "versions": [ { "version": "6.14", "status": "affected" }, { "version": "0", "lessThan": "6.14", "status": "unaffected", "versionType": "semver" }, { "version": "6.12.49", "lessThanOrEqual": "6.12.*", "status": "unaffected", "versionType": "semver" }, { "version": "6.16.9", "lessThanOrEqual": "6.16.*", "status": "unaffected", "versionType": "semver" }, { "version": "6.17", "lessThanOrEqual": "*", "status": "unaffected", "versionType": "original_commit_for_fix" } ] } ], "cpeApplicability": [ { "nodes": [ { "operator": "OR", "negate": false, "cpeMatch": [ { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.12.14", "versionEndExcluding": "6.12.49" }, { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.14", "versionEndExcluding": "6.16.9" }, { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.14", "versionEndExcluding": "6.17" }, { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.6.78" }, { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.13.3" } ] } ] } ], "references": [ { "url": "https://git.kernel.org/stable/c/80555adb5c892f0e21d243ae96ed997ee520aea9" }, { "url": "https://git.kernel.org/stable/c/fcb5718ebfe7fd64144e3399280440cce361a3ae" }, { "url": "https://git.kernel.org/stable/c/84bf1ac85af84d354c7a2fdbdc0d4efc8aaec34b" } ], "title": "ice: fix Rx page leak on multi-buffer frames", "x_generator": { "engine": "bippy-1.2.0" } } } }