CVE-2026-99164 is a memory corruption bug in the i.MX SPI controller driver, drivers/spi/spi-imx.c. Since Linux 7.0 the DMA buffer size the driver allocated was smaller than the cache-aligned length it mapped, so the DMA layer worked on memory past the end of each allocation. The fix is in mainline 7.3 and in stable 7.2.13; the 7.0 and 7.1 series ended without it, and kernels before 7.0 never contained the faulty code.
CVE-2026-99164 was published on 28 October 2026 for the i.MX SPI controller driver. For a team that ships an i.MX product, the first questions are whether the kernel in the product is in the affected range, whether the product uses the path that contains the bug, and which release carries the fix. This article answers those, then takes the bug apart, because the mistake behind this CVE is a DMA buffer size that does not match the mapped length, and any driver author who allocates a bounce buffer can make it.
- Component:
drivers/spi/spi-imx.c, built withCONFIG_SPI_IMX; ECSPI controllers in DMA mode. - Introduced in: Linux 7.0.
- Fixed in: mainline 7.3 and stable 7.2.13. Not fixed in 7.0.y or 7.1.y.
- Not affected: 6.18 and older longterm series.
- Severity: CVSS 3.1 base score 7.8 (High), assigned by the Linux kernel CNA in the CVE record; vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. NVD has not published its own score. - Reached by: ordinary DMA-mode SPI transfers.
- Effect: corruption of neighbouring kernel memory.
- Exploitation: not in the CISA Known Exploited Vulnerabilities catalogue on 13 November 2026.
What the code does: bounce buffers for ECSPI DMA transfers
The i.MX ECSPI controller can move data with DMA in place of interrupt-driven FIFO access. In spi-imx.c, spi_imx_can_dma() decides per transfer whether DMA is used. It requires a controller variant with DMA support, DMA channels acquired at probe, a transfer at least as long as the FIFO (64 bytes on the ECSPI variants), and 8, 16 or 32 bits per word.
Linux 7.0 added a change titled “spi: imx: support dynamic burst length for ECSPI DMA mode”. With it, the driver programs a burst of up to 512 bytes. The hardware places a partial word first when the length is not a multiple of four bytes, so the driver can no longer hand the caller’s buffer to the DMA engine directly. It allocates its own transmit and receive buffers, copies and reorders the data, and maps those private buffers.
Three functions matter. spi_imx_dma_tx_data_handle() computes the length and allocates both buffers. spi_imx_dma_map() maps them with dma_map_single(). spi_imx_dma_unmap() releases the mappings after the transfer, and spi_imx_dma_package_transfer() synchronises the receive buffer for the CPU before the data is copied back to the caller.
The bug: a DMA buffer size smaller than the mapped length
The allocation used one length. Every mapping call used another. From the pre-fix source, the allocation side reads:
dma_data->dma_len = ALIGN(dma_data->data_len, BYTES_PER_32BITS_WORD);
...
dma_data->dma_tx_buf = kzalloc(dma_data->dma_len, GFP_KERNEL);
...
dma_data->dma_rx_buf = kzalloc(dma_data->dma_len, GFP_KERNEL);and the mapping side reads:
/* Align to cache line to avoid swiotlo bounce */
#define DMA_CACHE_ALIGNED_LEN(x) ALIGN((x), dma_get_cache_alignment())
...
dma_data->dma_rx_addr = dma_map_single(rx_dev, dma_data->dma_rx_buf,
DMA_CACHE_ALIGNED_LEN(dma_data->dma_len),
DMA_FROM_DEVICE);The buffer length is rounded up to four bytes. The mapped length is rounded up to the DMA cache alignment, which on arm64 is the CPU cache line size and on 32-bit Arm is L1_CACHE_BYTES. The second value is larger than the first for most transfer lengths. The CVE record gives a plain case: on arm64 a 64-byte transfer allocated 64 bytes and mapped 128.
The length given to dma_map_single() is a statement to the DMA layer that the driver owns that many bytes starting at the buffer address. The DMA layer is entitled to perform cache maintenance on the whole range, and to copy the whole range where it substitutes a bounce buffer of its own. When the range extends past the allocation, those operations touch whatever the slab allocator placed next.
The commit message records what this looked like on a real product: a custom i.MX8MP board with a CAN controller on SPI, which failed after anything between 30 minutes and 30 hours under the same load. The neighbour there was a struct rpmsg_endpoint, an object of the remote-processor messaging code that has no connection to SPI. A hardware watchpoint showed its reference count changing from 2 to 1 from the SPI interrupt thread. The endpoint was then freed while still in use, the kernel printed refcount_t warnings, and the sequence ended in an “Unable to handle kernel paging request” oops.
How it is reached
The affected hardware is any SoC whose ECSPI node binds to the driver’s fsl,imx51-ecspi, fsl,imx53-ecspi or fsl,imx6ul-ecspi entries with DMA channels described in the device tree. The i.MX8MP device tree in mainline uses the fsl,imx6ul-ecspi fallback. The kernel configuration option is CONFIG_SPI_IMX, and the older CSPI variants in the same driver do not use DMA mode.
No unusual input is required. Any transfer that takes the DMA path on an affected kernel uses the mismatched lengths, whether it comes from an in-kernel client driver such as the mcp251xfd CAN driver in the report or from user space through spidev. The CVE record describes the attack vector as local: an account with access to an existing /dev/spidev node can submit a qualifying transfer, and the record adds that a local user who can send CAN frames on an interface served by an SPI CAN controller reaches the same path.
The fix
The fix, by Richard Leitner, changes three lines. Both allocations now use the mapped length:
- dma_data->dma_tx_buf = kzalloc(dma_data->dma_len, GFP_KERNEL);
+ dma_data->dma_tx_buf = kzalloc(DMA_CACHE_ALIGNED_LEN(dma_data->dma_len),
+ GFP_KERNEL);
...
- dma_data->dma_rx_buf = kzalloc(dma_data->dma_len, GFP_KERNEL);
+ dma_data->dma_rx_buf = kzalloc(DMA_CACHE_ALIGNED_LEN(dma_data->dma_len),
+ GFP_KERNEL);and the receive synchronisation uses the same length as map and unmap:
dma_sync_single_for_cpu(controller->dma_rx->device->dev, dma_data->dma_rx_addr,
- dma_data->dma_len, DMA_FROM_DEVICE);
+ DMA_CACHE_ALIGNED_LEN(dma_data->dma_len),
+ DMA_FROM_DEVICE);This is sufficient because the defect was a disagreement between two lengths, and the patch makes the DMA buffer size at least as large as every range later passed to the DMA API. The number of bytes sent on the wire is unchanged; dma_len still determines the transfer and the copy back to the caller. No later change to the fix existed in mainline when this article was written.
Which kernels are fixed
This CVE appeared in our round-up Linux Kernel CVEs: What to Patch by Device (25 – 31 Oct 2026). The state of each series, read from the trees on 13 November 2026:
- Mainline: fixed in 7.3 by commit
59eb22641f0c. The 7.3.y stable series, at 7.3.4, contains it from its first release. - 7.2.y: fixed in 7.2.13 by commit
2777f6637da0. The series has since reached end of life at 7.2.15. - 7.1.y and 7.0.y: affected and not fixed. Both series ended, at 7.1.13 and 7.0.14, before the fix was written.
- 6.18, 6.12, 6.6, 6.1, 5.15 and 5.10 longterm: not affected. The newest release of each series does not contain the dynamic burst code, so there is nothing to backport.
A vendor kernel that carries the dynamic burst change on an older base has the wrong DMA buffer size as well. Vendor trees were not examined for this article.
Check your tree
A version number does not settle the question for a BSP, so check the source. In the kernel tree, search the history for the fix by its subject:
raghu@techveda.org:~$ git log --oneline --grep="spi: imx: fix slab corruption from undersized DMA bounce buffers" -- drivers/spi/spi-imx.cA tree without usable history can be checked by counting the uses of the alignment macro:
raghu@techveda.org:~$ grep -c "DMA_CACHE_ALIGNED_LEN" drivers/spi/spi-imx.cIn the upstream trees read for this article the count is 0 where the faulty code was never present, 6 where it is present and unfixed, and 9 where the fix is applied. A vendor tree with other local changes may differ, so confirm by reading the two kzalloc() calls in spi_imx_dma_tx_data_handle(), where the DMA buffer size is set.
The lesson: one length, computed once
The pattern to look for in your own drivers is a buffer whose DMA buffer size is computed in one place and whose mapped, synchronised or unmapped length is computed in another. Here the allocation was written in terms of the hardware word and the mapping in terms of the cache line, and both were reasonable. The defect existed only in the relation between them.
- For every
dma_map_single(),dma_sync_single_for_cpu(),dma_sync_single_for_device()anddma_unmap_single()on a buffer, confirm that the length argument is the same expression and is not larger than the allocation. - Store the mapped length in the structure next to the buffer pointer and use that field everywhere, so that the rounding is applied once.
- Review every rounding macro applied at the point of use. If a length must be rounded up for the DMA layer, round the DMA buffer size before allocating.
What was checked and what could not be verified
Read for this article on 13 November 2026: the CVE announcement text and version data from the kernel CVE repository, the CVE record, the mainline fix commit and its 7.2.y backport, the commit that introduced the code, and spi-imx.c at the newest tag of every series named above. The CISA Known Exploited Vulnerabilities catalogue, version 2026.11.10, does not list CVE-2026-99164.
The score of 7.8 comes from the CVE record itself, last updated on 31 October, where the Linux kernel CNA supplied it. NVD displays it as a CNA score. NVD has not published its own analysis; its record has the status “Received”.
Three things were not verified. The public list archive of the announcement could not be opened, so the announcement was read from the repository that generates it. The exact operation that altered the neighbouring object is described differently in the two sources: the commit message reports a memory copy in the SPI interrupt thread, and the CVE record describes cache maintenance over the oversized range. Nothing here was reproduced on hardware.
Key takeaways
- CVE-2026-99164 is a DMA buffer size that is smaller than the mapped length in
spi-imx.c; check the source of your tree, not only its version number. - Normal DMA-mode SPI transfers reach it, so a product can corrupt its own kernel memory without any hostile user.
- In your own drivers, allocate, map, synchronise and unmap with one stored length.
Frequently asked questions
Which kernel versions are affected by CVE-2026-99164?
The wrong DMA buffer size is present in kernels from 7.0 up to, but not including, 7.2.13 and 7.3. The 7.0.y and 7.1.y series ended without the fix. The 6.18, 6.12, 6.6, 6.1, 5.15 and 5.10 longterm series do not contain the faulty code.
Which hardware does the bug affect?
i.MX SoCs whose ECSPI controller binds to the DMA-capable entries of the spi-imx driver and has DMA channels in the device tree. The report came from a custom i.MX8MP board. The driver is built with CONFIG_SPI_IMX.
How can I tell whether my BSP has the fix?
Search the history of drivers/spi/spi-imx.c for the commit subject “spi: imx: fix slab corruption from undersized DMA bounce buffers”, or confirm that both kzalloc() calls in spi_imx_dma_tx_data_handle() set the DMA buffer size with DMA_CACHE_ALIGNED_LEN().
Is CVE-2026-99164 known to be exploited?
The CISA Known Exploited Vulnerabilities catalogue, version 2026.11.10, does not list it. NVD has not published its own analysis of the record.
References
- Mainline fix: “spi: imx: fix slab corruption from undersized DMA bounce buffers” (59eb22641f0c)
- Stable 7.2.13 backport (2777f6637da0)
- Introducing commit: “spi: imx: support dynamic burst length for ECSPI DMA mode” (faa8e404ad8e)
- CVE-2026-99164 announcement text, kernel CVE repository
- CVE-2026-99164 record, cve.org
- CVE-2026-99164, National Vulnerability Database
- CISA Known Exploited Vulnerabilities catalogue
- drivers/spi/spi-imx.c at v7.3.4
- kernel.org release status (releases.json)




