[PATCH] accel/ethosu: validate all tiles and the packed-channel peak in the cmdstream verifier
From: Doruk Tan Ozturk
Date: Mon Jul 20 2026 - 15:22:56 EST
The command-stream verifier records region_size[], the maximum byte
offset the NPU will touch in each buffer object, and the submit path
rejects a job whose region_size exceeds the bound BO
(region_size[i] > gem->size). feat_matrix_length() computed that maximum
by evaluating the touched address at a single presumed-worst corner
(x = width, y = height, c = depth). That is the true maximum only when
the address is monotonic in x/y/c and the feature matrix occupies a
single tile. Neither always holds:
- Storage modes 0 and 1 split the feature matrix into up to four tiles,
each with an independent, user-supplied base (base[0..3]) into the
same region. feat_matrix_length() is evaluated once, so only the tile
containing the (max x, max y) corner is bounded; a large offset placed
in any other tile base is never accounted for.
- In the nhcwb16 packed format the channel term
(c / 16) * stride_c + (c & 0xf) * element_size is not monotonic in c
when stride_c is small, so the largest offset can lie at an interior
channel rather than at c = depth.
An unprivileged process able to open the accelerator node can therefore
craft a cmdstream whose real maximum output offset exceeds the computed
region_size, pass the submit check with an undersized output buffer, and
have the NPU DMA-write past the end of that buffer (up to ~1 MiB out of
bounds in the tiling case).
Evaluate the extent at every corner that can bound the populated tiles
and at the packed-channel peak, take the maximum, and reject the stream
if any reachable tile base is left unset. Well-formed single-tile,
monotonic streams are unchanged: the computed size is identical.
Found by 0sec automated security-research tooling (https://0sec.ai).
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: 0sec:multi-model
Signed-off-by: Doruk Tan Ozturk <doruk@xxxxxxx>
---
drivers/accel/ethosu/ethosu_gem.c | 73 +++++++++++++++++++++++++++----
1 file changed, 65 insertions(+), 8 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 3401883e207f..dfc499d7fb0c 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -192,17 +192,19 @@ static u64 dma_length(struct ethosu_validated_cmdstream_info *info,
return len;
}
-static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info,
- struct feat_matrix *fm,
- u32 x, u32 y, u32 c)
+/*
+ * Compute the byte offset the NPU addresses for one feature-matrix coordinate
+ * (x, y, c). Storage modes 0 and 1 route disjoint (x, y) sub-rectangles to up
+ * to four independent tile bases; the caller must only pass coordinates whose
+ * tiles it means to bound. Returns U64_MAX if the coordinate lands on a tile
+ * whose base was never set by the command stream.
+ */
+static u64 feat_matrix_addr(struct feat_matrix *fm, u32 x, u32 y, u32 c)
{
u32 element_size, storage = fm->precision >> 14;
int tile = 0;
u64 addr;
- if (fm->region < 0)
- return U64_MAX;
-
switch (storage) {
case 0:
if (x >= fm->width0 + 1) {
@@ -240,11 +242,66 @@ static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info,
break;
}
- info->region_size[fm->region] = max(info->region_size[fm->region], addr + 1);
-
return addr;
}
+/*
+ * Bound the region a feature matrix touches over the coordinate box
+ * [0, x] x [0, y] x [0, c].
+ *
+ * The address is separable and, as the strides are unsigned 40-bit quantities,
+ * non-decreasing in x and y, so their maxima sit at the box edge and, for the
+ * tiled storage modes, just below each split (width0, height[]) where an
+ * independent tile base takes over. Only nhcwb16 packing is non-monotonic in c:
+ * crossing a 16-lane group adds stride_c but resets the in-group term, so the
+ * peak may sit at the top of the last full group instead of at c. Evaluating
+ * every such extremal corner bounds all up-to-four tiles, whereas a single
+ * presumed-max corner undercounts the other tiles and the channel peak.
+ */
+static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info,
+ struct feat_matrix *fm,
+ u32 x, u32 y, u32 c)
+{
+ u32 storage = fm->precision >> 14;
+ u32 xs[2], ys[3], cs[2];
+ int nx = 0, ny = 0, nc = 0, i, j, k;
+ u64 max_addr = 0;
+
+ if (fm->region < 0)
+ return U64_MAX;
+
+ xs[nx++] = x;
+ if (storage == 0)
+ xs[nx++] = min_t(u32, x, fm->width0);
+
+ ys[ny++] = y;
+ if (storage == 0 || storage == 1) {
+ ys[ny++] = min_t(u32, y, fm->height[0]);
+ ys[ny++] = min_t(u32, y, fm->height[1]);
+ }
+
+ cs[nc++] = c;
+ if (((fm->precision >> 6) & 0x3) == 1 && c >= 16)
+ cs[nc++] = (c & ~0xfu) - 1;
+
+ for (i = 0; i < nx; i++) {
+ for (j = 0; j < ny; j++) {
+ for (k = 0; k < nc; k++) {
+ u64 addr = feat_matrix_addr(fm, xs[i], ys[j], cs[k]);
+
+ if (addr == U64_MAX)
+ return U64_MAX;
+ max_addr = max(max_addr, addr);
+ }
+ }
+ }
+
+ info->region_size[fm->region] =
+ max(info->region_size[fm->region], max_addr + 1);
+
+ return max_addr;
+}
+
static int calc_sizes(struct drm_device *ddev,
struct ethosu_validated_cmdstream_info *info,
u16 op, struct cmd_state *st,
--
2.43.0