* [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts
@ 2026-06-14 15:56 Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 1/6] media: v4l2-ctrls: validate HEVC and AV1 " Michael Bommarito
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 15:56 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne
Cc: Laurent Pinchart, Benjamin Gaignard, Detlev Casanova,
Ezequiel Garcia, Yunfei Dong, Jonas Karlman, Heiko Stuebner,
Kees Cook, linux-media, linux-rockchip, linux-mediatek,
linux-kernel
The stateless HEVC and AV1 V4L2 controls carry tile counts that several SoC
decoder drivers consume as loop bounds when laying out fixed-size hardware
descriptor buffers, but std_validate_compound() does not bound them. A
process with access to a /dev/videoN stateless decoder (on a typical desktop
or SBC, the active-seat local user via the logind/udev uaccess ACL, no extra
capability) can set a HEVC PPS or AV1 frame control with a tile count far
beyond the array capacity the drivers assume, making them loop past their
fixed buffers.
Patch 1 caps the HEVC and AV1 tile counts to the uAPI array capacity in the
core. Patches 2-5 add matching bounds in the consuming driver loops (and
bound the driver-interpreted HEVC pic_parameter_set_id / AV1
context_update_tile_id indices the core does not reject). Patch 6 adds KUnit
tests for the core validation.
I did my best to reproduce these with the hardware I have, but apologies in
advance if I missed something because the soft repro.
Test matrix: patch 1 + patch 6 run the real std_validate_compound() under
KASAN on x86_64 (rejects out-of-range counts, in-range and the zeroed
default pass, stock and patched); all objects build clean W=1; the ARM SoC
decoders are not reachable on the x86 host, so the per-driver writes are not
reproduced here.
Changes since v1:
- Patch 1: only reject AV1 tile_cols/tile_rows above the array capacity;
do not reject a zero count. v1 also rejected tile_cols < 1, which made
std_validate_compound() return -EINVAL for the zero-initialised AV1
frame control that userspace and v4l2-compliance submit, regressing the
visl compliance run (reported by the linux-media CI). The divide-by-zero
that a zero tile_cols would cause is guarded where the divisor is used in
the rockchip decoder (patch 4), so no functional protection is lost.
- Patch 6: drop the "zero tile_cols is rejected" KUnit case (no longer the
core's behaviour) and add a benign case asserting the zero-initialised
AV1 frame still validates, plus a tile_rows upper-bound case.
- No changes to patches 2-5.
Michael Bommarito (6):
media: v4l2-ctrls: validate HEVC and AV1 tile counts
media: rkvdec: bound HEVC tile loops and PPS id to the array capacity
media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer
capacity
media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard
divisor
media: mediatek: vcodec: bound AV1 tile-start copy to the array
capacity
media: v4l2-ctrls: add KUnit tests for compound control tile
validation
.../vcodec/decoder/vdec/vdec_av1_req_lat_if.c | 5 +-
.../rockchip/rkvdec/rkvdec-hevc-common.c | 22 ++-
.../platform/rockchip/rkvdec/rkvdec-hevc.c | 8 +-
.../rockchip/rkvdec/rkvdec-vdpu381-hevc.c | 2 +
.../platform/verisilicon/hantro_g2_hevc_dec.c | 6 +
.../verisilicon/rockchip_vpu981_hw_av1_dec.c | 29 ++--
drivers/media/v4l2-core/Kconfig | 12 ++
.../media/v4l2-core/v4l2-ctrls-core-test.c | 130 ++++++++++++++++++
drivers/media/v4l2-core/v4l2-ctrls-core.c | 27 ++++
9 files changed, 224 insertions(+), 17 deletions(-)
create mode 100644 drivers/media/v4l2-core/v4l2-ctrls-core-test.c
base-commit: 5200f5f493f79f14bbdc349e402a40dfb32f23c8
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/6] media: v4l2-ctrls: validate HEVC and AV1 tile counts
2026-06-14 15:56 [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
@ 2026-06-14 15:56 ` Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 2/6] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity Michael Bommarito
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 15:56 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne
Cc: Laurent Pinchart, Benjamin Gaignard, Detlev Casanova,
Ezequiel Garcia, Yunfei Dong, Jonas Karlman, Heiko Stuebner,
Kees Cook, linux-media, linux-rockchip, linux-mediatek,
linux-kernel
The stateless HEVC and AV1 controls carry tile counts that several SoC
decoder drivers consume as loop bounds when laying out fixed-size hardware
descriptor buffers, but std_validate_compound() does not bound them.
For V4L2_CTRL_TYPE_HEVC_PPS with tiling enabled, num_tile_columns_minus1
and num_tile_rows_minus1 (u8) drive loops over column_width_minus1[20] and
row_height_minus1[22]. For V4L2_CTRL_TYPE_AV1_FRAME, tile_info.tile_cols
and tile_rows (u8) bound loops over the mi_*_starts[] / *_in_sbs_minus_1[]
arrays. Reject counts beyond the uAPI array capacity with -EINVAL.
These are active-count fields (loop bounds), so bounding the upper limit
here mirrors the existing num_active_dpb_entries check. Only the upper
bound is enforced; a zero tile count is left to the consuming driver, so
the zero-initialised AV1 frame control that existing userspace submits is
not rejected, and the AV1 divisor (context_update_tile_id / tile_cols) is
guarded where it is used in the rockchip decoder (patch 4).
Driver-interpreted index values (HEVC pic_parameter_set_id, AV1
context_update_tile_id) are bounded in the consuming drivers instead
(patches 2 and 4).
Fixes: 256fa3920874 ("media: v4l: Add definitions for HEVC stateless decoding")
Fixes: 9de30f579980 ("media: Add AV1 uAPI")
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-8
---
drivers/media/v4l2-core/v4l2-ctrls-core.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c
index 6b375720e395c..58e2eb7002a19 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls-core.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c
@@ -790,10 +790,25 @@ static int validate_av1_film_grain(struct v4l2_ctrl_av1_film_grain *fg)
return 0;
}
+static int validate_av1_tile_info(struct v4l2_av1_tile_info *t)
+{
+ /* Loop bounds in the stateless AV1 drivers. */
+ if (t->tile_cols > V4L2_AV1_MAX_TILE_COLS)
+ return -EINVAL;
+
+ if (t->tile_rows > V4L2_AV1_MAX_TILE_ROWS)
+ return -EINVAL;
+
+ return 0;
+}
+
static int validate_av1_frame(struct v4l2_ctrl_av1_frame *f)
{
int ret = 0;
+ ret = validate_av1_tile_info(&f->tile_info);
+ if (ret)
+ return ret;
ret = validate_av1_quantization(&f->quantization);
if (ret)
return ret;
@@ -1242,6 +1257,14 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx,
p_hevc_pps->flags &=
~V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED;
+ } else {
+ /* Loop bounds in the stateless HEVC drivers. */
+ if (p_hevc_pps->num_tile_columns_minus1 >=
+ ARRAY_SIZE(p_hevc_pps->column_width_minus1))
+ return -EINVAL;
+ if (p_hevc_pps->num_tile_rows_minus1 >=
+ ARRAY_SIZE(p_hevc_pps->row_height_minus1))
+ return -EINVAL;
}
if (p_hevc_pps->flags &
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 2/6] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity
2026-06-14 15:56 [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 1/6] media: v4l2-ctrls: validate HEVC and AV1 " Michael Bommarito
@ 2026-06-14 15:56 ` Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 3/6] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity Michael Bommarito
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 15:56 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne
Cc: Laurent Pinchart, Benjamin Gaignard, Detlev Casanova,
Ezequiel Garcia, Yunfei Dong, Jonas Karlman, Heiko Stuebner,
Kees Cook, linux-media, linux-rockchip, linux-mediatek,
linux-kernel
compute_tiles_uniform() / compute_tiles_non_uniform() and assemble_hw_pps()
loop over num_tile_columns_minus1 / num_tile_rows_minus1 to write the
per-tile column_width[] / row_height[] arrays, sized to the PPS uAPI arrays
column_width_minus1[20] / row_height_minus1[22]; bound the loops to that
capacity. assemble_hw_pps() also indexes the fixed param_set[] table by
pic_parameter_set_id, a driver-interpreted index the core does not reject;
bound it to the table size before the access.
Fixes: 3595375c2301 ("media: rkvdec: Add HEVC backend")
Fixes: c9a59dc2acc7 ("media: rkvdec: Add HEVC support for the VDPU381 variant")
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-8
---
.../rockchip/rkvdec/rkvdec-hevc-common.c | 22 +++++++++++++++----
.../platform/rockchip/rkvdec/rkvdec-hevc.c | 8 +++++--
.../rockchip/rkvdec/rkvdec-vdpu381-hevc.c | 2 ++
3 files changed, 26 insertions(+), 6 deletions(-)
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c
index 3119f3bc9f98b..d0f26f736a763 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c
@@ -37,15 +37,22 @@ void compute_tiles_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size,
s32 pic_in_cts_height, u16 *column_width, u16 *row_height)
{
const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+ unsigned int num_cols, num_rows;
int i;
- for (i = 0; i < pps->num_tile_columns_minus1 + 1; i++)
+ /* Bound the loops to the column_width[]/row_height[] capacity. */
+ num_cols = min_t(unsigned int, pps->num_tile_columns_minus1 + 1,
+ ARRAY_SIZE(pps->column_width_minus1));
+ num_rows = min_t(unsigned int, pps->num_tile_rows_minus1 + 1,
+ ARRAY_SIZE(pps->row_height_minus1));
+
+ for (i = 0; i < num_cols; i++)
column_width[i] = ((i + 1) * pic_in_cts_width) /
(pps->num_tile_columns_minus1 + 1) -
(i * pic_in_cts_width) /
(pps->num_tile_columns_minus1 + 1);
- for (i = 0; i < pps->num_tile_rows_minus1 + 1; i++)
+ for (i = 0; i < num_rows; i++)
row_height[i] = ((i + 1) * pic_in_cts_height) /
(pps->num_tile_rows_minus1 + 1) -
(i * pic_in_cts_height) /
@@ -57,17 +64,24 @@ void compute_tiles_non_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size
s32 pic_in_cts_height, u16 *column_width, u16 *row_height)
{
const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+ unsigned int num_cols, num_rows;
s32 sum = 0;
int i;
- for (i = 0; i < pps->num_tile_columns_minus1; i++) {
+ /* Leave one slot for the trailing last-tile entry written below. */
+ num_cols = min_t(unsigned int, pps->num_tile_columns_minus1,
+ ARRAY_SIZE(pps->column_width_minus1) - 1);
+ num_rows = min_t(unsigned int, pps->num_tile_rows_minus1,
+ ARRAY_SIZE(pps->row_height_minus1) - 1);
+
+ for (i = 0; i < num_cols; i++) {
column_width[i] = pps->column_width_minus1[i] + 1;
sum += column_width[i];
}
column_width[i] = pic_in_cts_width - sum;
sum = 0;
- for (i = 0; i < pps->num_tile_rows_minus1; i++) {
+ for (i = 0; i < num_rows; i++) {
row_height[i] = pps->row_height_minus1[i] + 1;
sum += row_height[i];
}
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c
index ac8b825d080a2..29b5adb509727 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c
@@ -156,6 +156,8 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx,
* packet unit). so the driver copy SPS/PPS information to the exact PPS
* packet unit for HW accessing.
*/
+ if (pps->pic_parameter_set_id >= ARRAY_SIZE(priv_tbl->param_set))
+ return;
hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id];
memset(hw_ps, 0, sizeof(*hw_ps));
@@ -274,9 +276,11 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx,
if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) {
/* Userspace also provide column width and row height for uniform spacing */
- for (i = 0; i <= pps->num_tile_columns_minus1; i++)
+ for (i = 0; i <= pps->num_tile_columns_minus1 &&
+ i < ARRAY_SIZE(pps->column_width_minus1); i++)
WRITE_PPS(pps->column_width_minus1[i], COLUMN_WIDTH(i));
- for (i = 0; i <= pps->num_tile_rows_minus1; i++)
+ for (i = 0; i <= pps->num_tile_rows_minus1 &&
+ i < ARRAY_SIZE(pps->row_height_minus1); i++)
WRITE_PPS(pps->row_height_minus1[i], ROW_HEIGHT(i));
} else {
WRITE_PPS(((sps->pic_width_in_luma_samples + ctb_size_y - 1) / ctb_size_y) - 1,
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c
index fe6414a175510..6dafa1dd28507 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c
@@ -145,6 +145,8 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx,
* packet unit). so the driver copy SPS/PPS information to the exact PPS
* packet unit for HW accessing.
*/
+ if (pps->pic_parameter_set_id >= ARRAY_SIZE(priv_tbl->param_set))
+ return;
hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id];
memset(hw_ps, 0, sizeof(*hw_ps));
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 3/6] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity
2026-06-14 15:56 [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 1/6] media: v4l2-ctrls: validate HEVC and AV1 " Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 2/6] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity Michael Bommarito
@ 2026-06-14 15:56 ` Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 4/6] media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard divisor Michael Bommarito
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 15:56 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne
Cc: Laurent Pinchart, Benjamin Gaignard, Detlev Casanova,
Ezequiel Garcia, Yunfei Dong, Jonas Karlman, Heiko Stuebner,
Kees Cook, linux-media, linux-rockchip, linux-mediatek,
linux-kernel
prepare_tile_info_buffer() writes one entry per tile into the tile_sizes
DMA buffer, sized for a grid equal to the PPS uAPI array capacity. Bound
the loop to that capacity so the writes stay inside the buffer.
Fixes: cb5dd5a0fa51 ("media: hantro: Introduce G2/HEVC decoder")
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-8
---
drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
index e8c2e83379def..94fbd79885aa5 100644
--- a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
@@ -22,6 +22,12 @@ static void prepare_tile_info_buffer(struct hantro_ctx *ctx)
bool tiles_enabled, uniform_spacing;
u32 no_chroma = 0;
+ /* Bound the loops to the tile_sizes buffer capacity. */
+ num_tile_cols = min_t(unsigned int, num_tile_cols,
+ ARRAY_SIZE(pps->column_width_minus1));
+ num_tile_rows = min_t(unsigned int, num_tile_rows,
+ ARRAY_SIZE(pps->row_height_minus1));
+
tiles_enabled = !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED);
uniform_spacing = !!(pps->flags & V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING);
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 4/6] media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard divisor
2026-06-14 15:56 [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
` (2 preceding siblings ...)
2026-06-14 15:56 ` [PATCH v2 3/6] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity Michael Bommarito
@ 2026-06-14 15:56 ` Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 6/6] media: v4l2-ctrls: add KUnit tests for compound control tile validation Michael Bommarito
5 siblings, 0 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 15:56 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne
Cc: Laurent Pinchart, Benjamin Gaignard, Detlev Casanova,
Ezequiel Garcia, Yunfei Dong, Jonas Karlman, Heiko Stuebner,
Kees Cook, linux-media, linux-rockchip, linux-mediatek,
linux-kernel
rockchip_vpu981_av1_dec_set_tile_info() divides context_update_tile_id by
tile_info->tile_cols and writes one descriptor per tile into the tile_info
DMA buffer, sized for AV1_MAX_TILES. tile_cols / tile_rows come straight
from the bitstream; reject a zero column or row count and bound the grid to
AV1_MAX_TILES so the division is safe and the writes stay in the buffer.
Fixes: 727a400686a2 ("media: verisilicon: Add Rockchip AV1 decoder")
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-8
---
.../verisilicon/rockchip_vpu981_hw_av1_dec.c | 29 +++++++++++++------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c
index e4e21ad373233..71d2ef72c4402 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c
@@ -578,21 +578,32 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx)
const struct v4l2_av1_tile_info *tile_info = &ctrls->frame->tile_info;
const struct v4l2_ctrl_av1_tile_group_entry *group_entry =
ctrls->tile_group_entry;
- int context_update_y =
- tile_info->context_update_tile_id / tile_info->tile_cols;
- int context_update_x =
- tile_info->context_update_tile_id % tile_info->tile_cols;
- int context_update_tile_id =
- context_update_x * tile_info->tile_rows + context_update_y;
+ unsigned int tile_cols, tile_rows;
+ int context_update_y, context_update_x, context_update_tile_id;
u8 *dst = av1_dec->tile_info.cpu;
struct hantro_dev *vpu = ctx->dev;
int tile0, tile1;
+ /* Guard the divisor and bound the grid to the tile_info buffer. */
+ tile_cols = tile_info->tile_cols;
+ tile_rows = tile_info->tile_rows;
+ if (!tile_cols || !tile_rows)
+ return;
+ if (tile_cols * tile_rows > AV1_MAX_TILES) {
+ tile_cols = min_t(unsigned int, tile_cols, AV1_MAX_TILES);
+ tile_rows = min_t(unsigned int, tile_rows,
+ AV1_MAX_TILES / tile_cols);
+ }
+
+ context_update_y = tile_info->context_update_tile_id / tile_cols;
+ context_update_x = tile_info->context_update_tile_id % tile_cols;
+ context_update_tile_id = context_update_x * tile_rows + context_update_y;
+
memset(dst, 0, av1_dec->tile_info.size);
- for (tile0 = 0; tile0 < tile_info->tile_cols; tile0++) {
- for (tile1 = 0; tile1 < tile_info->tile_rows; tile1++) {
- int tile_id = tile1 * tile_info->tile_cols + tile0;
+ for (tile0 = 0; tile0 < tile_cols; tile0++) {
+ for (tile1 = 0; tile1 < tile_rows; tile1++) {
+ int tile_id = tile1 * tile_cols + tile0;
u32 start, end;
u32 y0 =
tile_info->height_in_sbs_minus_1[tile1] + 1;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity
2026-06-14 15:56 [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
` (3 preceding siblings ...)
2026-06-14 15:56 ` [PATCH v2 4/6] media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard divisor Michael Bommarito
@ 2026-06-14 15:56 ` Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 6/6] media: v4l2-ctrls: add KUnit tests for compound control tile validation Michael Bommarito
5 siblings, 0 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 15:56 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne
Cc: Laurent Pinchart, Benjamin Gaignard, Detlev Casanova,
Ezequiel Garcia, Yunfei Dong, Jonas Karlman, Heiko Stuebner,
Kees Cook, linux-media, linux-rockchip, linux-mediatek,
linux-kernel
vdec_av1_slice_setup_tile() copies tile_cols + 1 / tile_rows + 1 start
positions into mi_col_starts[] / mi_row_starts[], which hold
V4L2_AV1_MAX_TILE_COLS + 1 / V4L2_AV1_MAX_TILE_ROWS + 1 entries. tile_cols
and tile_rows come straight from the bitstream; bound the copy to the array
capacity so the accesses stay in range.
Fixes: 0934d3759615 ("media: mediatek: vcodec: separate decoder and encoder")
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-8
---
.../mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c
index 2d622e85f8271..49d9b4a72387e 100644
--- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c
+++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c
@@ -1299,11 +1299,12 @@ static void vdec_av1_slice_setup_tile(struct vdec_av1_slice_frame *frame,
tile->uniform_tile_spacing_flag =
BIT_FLAG(ctrl_tile, V4L2_AV1_TILE_INFO_FLAG_UNIFORM_TILE_SPACING);
- for (i = 0; i < tile->tile_cols + 1; i++)
+ /* Bound the copy to the mi_col_starts[]/mi_row_starts[] capacity. */
+ for (i = 0; i < tile->tile_cols + 1 && i < V4L2_AV1_MAX_TILE_COLS + 1; i++)
tile->mi_col_starts[i] =
ALIGN(ctrl_tile->mi_col_starts[i], BIT(mib_size_log2)) >> mib_size_log2;
- for (i = 0; i < tile->tile_rows + 1; i++)
+ for (i = 0; i < tile->tile_rows + 1 && i < V4L2_AV1_MAX_TILE_ROWS + 1; i++)
tile->mi_row_starts[i] =
ALIGN(ctrl_tile->mi_row_starts[i], BIT(mib_size_log2)) >> mib_size_log2;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 6/6] media: v4l2-ctrls: add KUnit tests for compound control tile validation
2026-06-14 15:56 [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
` (4 preceding siblings ...)
2026-06-14 15:56 ` [PATCH v2 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity Michael Bommarito
@ 2026-06-14 15:56 ` Michael Bommarito
5 siblings, 0 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 15:56 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne
Cc: Laurent Pinchart, Benjamin Gaignard, Detlev Casanova,
Ezequiel Garcia, Yunfei Dong, Jonas Karlman, Heiko Stuebner,
Kees Cook, linux-media, linux-rockchip, linux-mediatek,
linux-kernel
Drive std_validate_compound() with crafted HEVC PPS and AV1 frame controls
and assert that out-of-range tile counts are rejected with -EINVAL while
in-range values pass, including the zero-initialised AV1 frame default that
userspace and v4l2-compliance submit. The tests are #included at the end of
v4l2-ctrls-core.c to reach the static helper, gated by
CONFIG_V4L2_CTRLS_KUNIT_TEST.
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-8
---
drivers/media/v4l2-core/Kconfig | 12 ++
.../media/v4l2-core/v4l2-ctrls-core-test.c | 130 ++++++++++++++++++
drivers/media/v4l2-core/v4l2-ctrls-core.c | 4 +
3 files changed, 146 insertions(+)
create mode 100644 drivers/media/v4l2-core/v4l2-ctrls-core-test.c
diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index d50ccac9733cc..04b15d860a0af 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -3,6 +3,18 @@
# Generic video config states
#
+config V4L2_CTRLS_KUNIT_TEST
+ bool "KUnit tests for V4L2 compound control validation" if !KUNIT_ALL_TESTS
+ depends on VIDEO_DEV && KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ This builds KUnit tests for the stateless-codec compound control
+ validation in std_validate_compound(). They check that out-of-range
+ HEVC/AV1 tile counts and parameter-set / tile indices are rejected
+ before driver code consumes them as array indices or loop bounds.
+
+ If unsure, say N.
+
config VIDEO_V4L2_I2C
bool
depends on I2C && VIDEO_DEV
diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core-test.c b/drivers/media/v4l2-core/v4l2-ctrls-core-test.c
new file mode 100644
index 0000000000000..813872694eb46
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-ctrls-core-test.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit tests for HEVC/AV1 tile-count validation in std_validate_compound().
+ * #included at the end of v4l2-ctrls-core.c to reach the static helper.
+ */
+
+#include <kunit/test.h>
+
+static int call_validate_compound(enum v4l2_ctrl_type type, void *payload,
+ u32 elem_size)
+{
+ struct v4l2_ctrl ctrl = {
+ .type = type,
+ .elem_size = elem_size,
+ };
+ union v4l2_ctrl_ptr ptr = { .p = payload };
+
+ return std_validate_compound(&ctrl, 0, ptr);
+}
+
+/* HEVC PPS: num_tile_columns_minus1 / num_tile_rows_minus1 bounds. */
+static void v4l2_ctrls_hevc_pps_tile_cols(struct kunit *test)
+{
+ struct v4l2_ctrl_hevc_pps *pps;
+
+ pps = kunit_kzalloc(test, sizeof(*pps), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pps);
+
+ pps->flags = V4L2_HEVC_PPS_FLAG_TILES_ENABLED;
+
+ /* In range: count == array capacity (minus1 == capacity - 1). */
+ pps->num_tile_columns_minus1 = ARRAY_SIZE(pps->column_width_minus1) - 1;
+ pps->num_tile_rows_minus1 = ARRAY_SIZE(pps->row_height_minus1) - 1;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_HEVC_PPS, pps,
+ sizeof(*pps)),
+ 0);
+
+ /* Out of range: one past the column array. */
+ pps->num_tile_columns_minus1 = ARRAY_SIZE(pps->column_width_minus1);
+ pps->num_tile_rows_minus1 = 0;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_HEVC_PPS, pps,
+ sizeof(*pps)),
+ -EINVAL);
+
+ /* Out of range: maximal attacker value. */
+ pps->num_tile_columns_minus1 = 0xff;
+ pps->num_tile_rows_minus1 = 0xff;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_HEVC_PPS, pps,
+ sizeof(*pps)),
+ -EINVAL);
+}
+
+/* AV1 frame: tile_cols / tile_rows upper bounds. */
+static void v4l2_ctrls_av1_frame_tile(struct kunit *test)
+{
+ struct v4l2_ctrl_av1_frame *f;
+
+ f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, f);
+
+ /*
+ * Benign control: a zero-initialised frame (tile_cols == 0) must
+ * still pass. Userspace and v4l2-compliance set the zeroed default,
+ * and the divisor that a zero tile_cols would feed is guarded in the
+ * consuming driver rather than rejected here.
+ */
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_AV1_FRAME, f,
+ sizeof(*f)),
+ 0);
+
+ /* In range: a 1x1 tiling. */
+ f->tile_info.tile_cols = 1;
+ f->tile_info.tile_rows = 1;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_AV1_FRAME, f,
+ sizeof(*f)),
+ 0);
+
+ /* In range: maximal legal tile_cols / tile_rows. */
+ f->tile_info.tile_cols = V4L2_AV1_MAX_TILE_COLS;
+ f->tile_info.tile_rows = V4L2_AV1_MAX_TILE_ROWS;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_AV1_FRAME, f,
+ sizeof(*f)),
+ 0);
+
+ /* Out of range: tile_cols past the array. */
+ f->tile_info.tile_cols = V4L2_AV1_MAX_TILE_COLS + 1;
+ f->tile_info.tile_rows = 1;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_AV1_FRAME, f,
+ sizeof(*f)),
+ -EINVAL);
+
+ /* Out of range: maximal attacker value. */
+ f->tile_info.tile_cols = 0xff;
+ f->tile_info.tile_rows = 0xff;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_AV1_FRAME, f,
+ sizeof(*f)),
+ -EINVAL);
+
+ /* Out of range: tile_rows past the array. */
+ f->tile_info.tile_cols = 1;
+ f->tile_info.tile_rows = V4L2_AV1_MAX_TILE_ROWS + 1;
+ KUNIT_EXPECT_EQ(test,
+ call_validate_compound(V4L2_CTRL_TYPE_AV1_FRAME, f,
+ sizeof(*f)),
+ -EINVAL);
+}
+
+static struct kunit_case v4l2_ctrls_test_cases[] = {
+ KUNIT_CASE(v4l2_ctrls_hevc_pps_tile_cols),
+ KUNIT_CASE(v4l2_ctrls_av1_frame_tile),
+ {}
+};
+
+static struct kunit_suite v4l2_ctrls_test_suite = {
+ .name = "v4l2-ctrls-compound",
+ .test_cases = v4l2_ctrls_test_cases,
+};
+
+kunit_test_suite(v4l2_ctrls_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for V4L2 stateless-codec compound control validation");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c
index 58e2eb7002a19..5e86b6373a136 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls-core.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c
@@ -2851,3 +2851,7 @@ int v4l2_ctrl_new_fwnode_properties(struct v4l2_ctrl_handler *hdl,
return hdl->error;
}
EXPORT_SYMBOL(v4l2_ctrl_new_fwnode_properties);
+
+#if IS_ENABLED(CONFIG_V4L2_CTRLS_KUNIT_TEST)
+#include "v4l2-ctrls-core-test.c"
+#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-06-14 15:56 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-14 15:56 [PATCH v2 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 1/6] media: v4l2-ctrls: validate HEVC and AV1 " Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 2/6] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 3/6] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 4/6] media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard divisor Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity Michael Bommarito
2026-06-14 15:56 ` [PATCH v2 6/6] media: v4l2-ctrls: add KUnit tests for compound control tile validation Michael Bommarito
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox