* [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts
@ 2026-06-14 13:09 Michael Bommarito
2026-06-14 13:09 ` [PATCH 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 13:09 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne, Sebastian Fricke
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 and divisors 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 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.
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 | 119 ++++++++++++++++++
drivers/media/v4l2-core/v4l2-ctrls-core.c | 27 ++++
9 files changed, 213 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 1/6] media: v4l2-ctrls: validate HEVC and AV1 tile counts
2026-06-14 13:09 [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
@ 2026-06-14 13:09 ` Michael Bommarito
2026-06-14 13:09 ` [PATCH 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 13:09 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne, Sebastian Fricke
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 and a zero tile_cols divides by zero. Cap both to the uAPI array
capacity and reject out-of-range values with -EINVAL.
These are active-count fields (loop bounds), so bounding them here mirrors
the existing num_active_dpb_entries check. 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
---
These are loop-bound counts, not per-entry index values, so bounding them
in the common path mirrors the existing num_active_dpb_entries check.
Tested with the KUnit suite in patch 6: under KASAN on x86_64 the new
checks reject the over-range HEVC/AV1 tile counts and the zero AV1
tile_cols with -EINVAL while the in-range cases still pass, on stock and
patched.
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 6b37572..25227d9 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 and a divisor in the stateless AV1 drivers. */
+ if (t->tile_cols < 1 || t->tile_cols > V4L2_AV1_MAX_TILE_COLS)
+ return -EINVAL;
+
+ if (t->tile_rows < 1 || 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 [flat|nested] 7+ messages in thread
* [PATCH 2/6] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity
2026-06-14 13:09 [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
2026-06-14 13:09 ` [PATCH 1/6] media: v4l2-ctrls: validate HEVC and AV1 " Michael Bommarito
@ 2026-06-14 13:09 ` Michael Bommarito
2026-06-14 13:10 ` [PATCH 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 13:09 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne, Sebastian Fricke
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
---
The decoder is an ARM SoC block (Rockchip RK33xx/RK35xx) not reachable on
the x86 KUnit host, so the driver-side out-of-bounds write is not
reproduced here.
.../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 3119f3b..d0f26f7 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 ac8b825..29b5adb 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 fe6414a..6dafa1d 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 [flat|nested] 7+ messages in thread
* [PATCH 3/6] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity
2026-06-14 13:09 [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
2026-06-14 13:09 ` [PATCH 1/6] media: v4l2-ctrls: validate HEVC and AV1 " Michael Bommarito
2026-06-14 13:09 ` [PATCH 2/6] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity Michael Bommarito
@ 2026-06-14 13:10 ` Michael Bommarito
2026-06-14 13:10 ` [PATCH 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 13:10 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne, Sebastian Fricke
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
---
This is an i.MX8M Hantro G2 SoC block not reachable on the x86 KUnit host,
so the driver-side out-of-bounds write is not reproduced here.
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 e8c2e83..94fbd79 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 [flat|nested] 7+ messages in thread
* [PATCH 4/6] media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard divisor
2026-06-14 13:09 [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
` (2 preceding siblings ...)
2026-06-14 13:10 ` [PATCH 3/6] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity Michael Bommarito
@ 2026-06-14 13:10 ` Michael Bommarito
2026-06-14 13:10 ` [PATCH 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity Michael Bommarito
2026-06-14 13:10 ` [PATCH 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 13:10 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne, Sebastian Fricke
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
---
This is a Rockchip RK35xx SoC block not reachable on the x86 KUnit host,
so the driver-side out-of-bounds write is not reproduced here.
.../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 e4e21ad..71d2ef7 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 [flat|nested] 7+ messages in thread
* [PATCH 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity
2026-06-14 13:09 [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
` (3 preceding siblings ...)
2026-06-14 13:10 ` [PATCH 4/6] media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard divisor Michael Bommarito
@ 2026-06-14 13:10 ` Michael Bommarito
2026-06-14 13:10 ` [PATCH 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 13:10 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne, Sebastian Fricke
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
---
This is an MT8195-class SoC block not reachable on the x86 KUnit host, so
the driver-side out-of-bounds access is not reproduced here.
.../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 2d622e8..49d9b4a 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 [flat|nested] 7+ messages in thread
* [PATCH 6/6] media: v4l2-ctrls: add KUnit tests for compound control tile validation
2026-06-14 13:09 [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
` (4 preceding siblings ...)
2026-06-14 13:10 ` [PATCH 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity Michael Bommarito
@ 2026-06-14 13:10 ` Michael Bommarito
5 siblings, 0 replies; 7+ messages in thread
From: Michael Bommarito @ 2026-06-14 13:10 UTC (permalink / raw)
To: Hans Verkuil, Mauro Carvalho Chehab, Sakari Ailus,
Nicolas Dufresne, Sebastian Fricke
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 and a zero AV1 tile_cols are
rejected with -EINVAL while in-range values pass. 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
---
The tests are #included at the end of v4l2-ctrls-core.c so they can reach
the static helper, gated by CONFIG_V4L2_CTRLS_KUNIT_TEST. Run under KASAN
on x86_64 they fail on the stock validator and pass after patch 1.
drivers/media/v4l2-core/Kconfig | 12 ++
.../media/v4l2-core/v4l2-ctrls-core-test.c | 119 ++++++++++++++++++
drivers/media/v4l2-core/v4l2-ctrls-core.c | 4 +
3 files changed, 135 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 d50ccac..04b15d8 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 0000000..6ec48fc
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-ctrls-core-test.c
@@ -0,0 +1,119 @@
+// 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 bounds and non-zero requirement. */
+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);
+
+ /* In range: a 1x1 tiling (a zeroed frame is otherwise valid). */
+ 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);
+
+ /* Divide-by-zero guard: tile_cols == 0 must be rejected. */
+ f->tile_info.tile_cols = 0;
+ f->tile_info.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 25227d9..8debd0e 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 [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-06-14 13:10 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-14 13:09 [PATCH 0/6] media: v4l2-ctrls: bound stateless HEVC/AV1 tile counts Michael Bommarito
2026-06-14 13:09 ` [PATCH 1/6] media: v4l2-ctrls: validate HEVC and AV1 " Michael Bommarito
2026-06-14 13:09 ` [PATCH 2/6] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity Michael Bommarito
2026-06-14 13:10 ` [PATCH 3/6] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity Michael Bommarito
2026-06-14 13:10 ` [PATCH 4/6] media: verisilicon: rockchip: bound VPU981 AV1 tile loop and guard divisor Michael Bommarito
2026-06-14 13:10 ` [PATCH 5/6] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity Michael Bommarito
2026-06-14 13:10 ` [PATCH 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