From: sashiko-bot@kernel.org
To: "Sofus Forstreuter" <sofus.c@icloud.com>
Cc: media-ci@linuxtv.org, conor+dt@kernel.org,
devicetree@vger.kernel.org, robh@kernel.org
Subject: Re: [PATCH v2 09/17] media: apple: avd: add hevc support
Date: Sat, 26 Sep 2026 13:35:02 +0000 [thread overview]
Message-ID: <20260926133503.276A71F000FF@smtp.kernel.org> (raw)
In-Reply-To: <20260926-avd-v2-9-ecebe6a3648e@icloud.com>
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [Critical] avd: heap buffer overflow due to missing array bounds validation on pps->column_width_minus1
- [High] avd: kernel panic and buffer overflow via unbounded sps->log2_diff shift
- [High] avd: kernel stack out-of-bounds read via unbounded pos variable in stream_slices
- [High] avd: double out-of-bounds array read in stream_slice_mv via sl->collocated_ref_idx
- [High] avd: heap out-of-bounds read via off-by-one check on slice_segment_addr
- [High] avd: arbitrary physical DMA read via integer underflow in slice size offset validation
- [High] avd: use-after-free and out-of-bounds read due to lockless dereference of V4L2 dynamic array pointers
- [High] avd: integer overflow in entry point offset validation allows heap out-of-bounds read
- [Medium] avd: hardware register corruption due to overlapping bitmasks for HEVC_TR_INTRA and HEVC_TR_INTER
--
commit a7dbbf6a1924ee26ecce4f5c5b73414f86b42959
Author: Sofus Forstreuter <sofus.c@icloud.com>
media: apple: avd: add hevc support
The fluster score is 143/147 for JCT-VC-HEVC_V1.
RPS_E_qualcomm_5, WPP_D_ericsson_MAIN_2 and WPP_D_ericsson_MAIN10_2 are
failing, but should be supported.
> diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c
> new file mode 100644
> index 0000000000000..d90c7770006b7
> --- /dev/null
> +++ b/drivers/media/platform/apple/avd/avd-hevc.c
> @@ -0,0 +1,1340 @@
[ ... ]
> +#define HEVC_TR_INTRA(v) FIELD_PREP(GENMASK(4, 1), v)
> +#define HEVC_TR_INTER(v) FIELD_PREP(GENMASK(7, 4), v)
[Severity: Medium]
Do these bitmasks overlap? It looks like both GENMASK(4, 1) and GENMASK(7, 4)
include bit 4. When these are ORed together in set_header() like so:
HEVC_TR_INTER(sps->max_transform_hierarchy_depth_inter) |
HEVC_TR_INTRA(sps->max_transform_hierarchy_depth_intra) |
could this clobber the overlapping bit and misconfigure the hardware decoder?
[ ... ]
> +static void stream_slice_mv(struct avd_ctx *ctx, struct avd_hevc_run *run,
> + const struct v4l2_ctrl_hevc_slice_params *sl,
> + bool is_first)
> +{
> + const struct v4l2_ctrl_hevc_decode_params *decode = run->decode;
> + struct avd_decoded_buffer *dst, *ref;
> + bool ref_valid;
> + const u8 *ref_list;
> + int ref_idx = 0;
> +
> + ref_list = sl->slice_type == V4L2_HEVC_SLICE_TYPE_P ? sl->ref_idx_l0 :
> + sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0 ?
> + sl->ref_idx_l0 :
> + sl->ref_idx_l1;
> +
> + if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_I) {
> + push(AVD_OP_SL_REF |
> + AVD_OP_SL_REF_SLICE_I(sl->slice_type ==
> + V4L2_HEVC_SLICE_TYPE_I),
> + "slc_a8c_cmd_ref_type");
> + return;
> + }
> + /* bidirectional prediction */
> + if (sl->collocated_ref_idx < V4L2_HEVC_DPB_ENTRIES_NUM_MAX &&
> + ref_list[sl->collocated_ref_idx] < V4L2_HEVC_DPB_ENTRIES_NUM_MAX)
> + ref_idx = ref_list[sl->collocated_ref_idx];
> +
> + dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf);
> + ref = avd_get_ref_buf(ctx, &dst->base.vb,
> + decode->dpb[ref_idx].timestamp);
> +
> + ref_valid = !(sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT) &&
> + (sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) &&
> + is_first && !ref->hevc.is_intra;
> +
> + push(AVD_OP_SL_REF |
> + AVD_OP_SL_REF_MAX_MERGE(
> + 5 - sl->five_minus_max_num_merge_cand) |
> + AVD_OP_SL_REF_FLAG_CABAC(
> + sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT) |
> + AVD_OP_SL_REF_FLAG0(
> + (sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) &&
> + !(sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0)) |
> + AVD_OP_SL_REF_FLAG1(
> + !(sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO)) |
> + AVD_OP_SL_REF_FLAG2(
> + (sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) ||
> + (sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT)) |
> + AVD_OP_SL_REF_NUM_L0(sl->num_ref_idx_l0_active_minus1) |
> + AVD_OP_SL_REF_NUM_L1(sl->num_ref_idx_l1_active_minus1) |
> + AVD_OP_SL_REF_SLICE_P(sl->slice_type ==
> + V4L2_HEVC_SLICE_TYPE_P) |
> + AVD_OP_SL_REF_SLICE_B(ref_valid),
> + "slc_a8c_cmd_ref_type");
> +
> + if (ref_valid) {
> + dma_addr_t mv_color_addr =
> + vb2_dma_contig_plane_dma_addr(&ref->base.vb.vb2_buf,
> + 0) +
> + (ref->base.vb.planes[0].length -
> + mv_color_size(fmt_width(ctx), fmt_height(ctx)));
> + pusha(mv_color_addr, "slc_bd4_sps_tile_addr2_lsb8",
> + decode->dpb[ref_list[sl->collocated_ref_idx]]
> + .pic_order_cnt_val);
[Severity: High]
Is it possible to have an out-of-bounds read here?
If sl->collocated_ref_idx (which is controlled by userspace) is >= 16
(V4L2_HEVC_DPB_ENTRIES_NUM_MAX), it seems to bypass the initial bounds check
which updates ref_idx. Later, when pushing the command:
decode->dpb[ref_list[sl->collocated_ref_idx]]
could this read ref_list out-of-bounds, yielding a garbage index, and then
subsequently use that to read out-of-bounds in decode->dpb, programming
arbitrary memory values into the hardware?
> + }
> +}
[ ... ]
> +static int submit_slice_segment(struct avd_ctx *ctx, struct avd_hevc_run *run,
> + const struct v4l2_ctrl_hevc_slice_params *sl,
> + int row, int col, u32 col_bd[23],
> + u32 row_bd[23], u32 pic_in_cts_width,
> + u32 pic_in_cts_height, bool first_slice,
> + bool hflip, bool vflip, u32 coded_flags,
> + u32 last_tile_block)
> +{
> + const struct v4l2_ctrl_hevc_pps *pps = run->pps;
> + u32 tb_x, tb_y, tile_block, tile_boundary;
> +
> + if (coded_flags & NEW_SLICE) {
> + tb_x = sl->slice_segment_addr % pic_in_cts_width;
> + tb_y = sl->slice_segment_addr / pic_in_cts_width;
> +
> + tile_block = AVD_OP_SL_LOC_Y(tb_y) | AVD_OP_SL_LOC_X(tb_x);
> +
> + if (!(sl->flags &
> + V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT))
> + last_tile_block = tile_block;
> +
> + /*
> + * tile block start
> + * CABAC window
> + */
> + push(AVD_OP_SL_LOC | last_tile_block, "cm3_cmd_set_cabac_xy");
> +
> + stream_slice_dqtblk(ctx, run, sl);
> + } else {
> + tile_boundary = AVD_OP_SL_LOC_Y(row_bd[row]) |
> + AVD_OP_SL_LOC_X(col_bd[col]);
[Severity: High]
Can the 'row' index here exceed the bounds of the 45-element row_bd kernel
stack array?
In stream_slices(), the row index is calculated from an unbounded 'pos'
counter which increments for each entry point offset:
row = pos / num_cols;
col = pos % num_cols;
Since there can be up to 1760 entry point offsets, 'row' is not bounded
against 44. Could this result in an out-of-bounds stack read?
> + }
[ ... ]
> +static void compute_tiles_non_uniform(struct avd_hevc_run *run,
> + u16 log2_min_cb_size, u16 width,
> + u16 height, u32 pic_in_cts_width,
> + u32 pic_in_cts_height, u16 *column_width,
> + u16 *row_height)
> +{
> + const struct v4l2_ctrl_hevc_pps *pps = run->pps;
> + u32 sum = 0;
> + int i;
> +
> + for (i = 0; i < pps->num_tile_columns_minus1; i++) {
> + column_width[i] = MIN(pps->column_width_minus1[i] + 1,
> + pic_in_cts_width);
> + sum += column_width[i];
> + }
> + column_width[i] = pic_in_cts_width - sum;
[Severity: Critical]
Does this code overflow column_width_minus1[] and also underflow
column_width[i]?
The validation allows pps->num_tile_columns_minus1 up to 39, but the V4L2
payload array column_width_minus1 only has 20 elements. This could cause an
out-of-bounds read of the struct.
Additionally, since sum is not validated, if sum exceeds pic_in_cts_width,
does pic_in_cts_width - sum underflow into a huge unsigned 16-bit integer?
This huge dimension seems to propagate into compute_bd() and compute_tile_ids(),
potentially causing thousands of out-of-bounds memory writes on the kernel heap
when indexing ctb_addr_rs_to_ts and tile_ids.
> +
> + sum = 0;
[ ... ]
> +static void stream_slices(struct avd_ctx *ctx, struct avd_hevc_run *run)
> +{
> + const struct v4l2_ctrl_hevc_sps *sps = run->sps;
> + const struct v4l2_ctrl_hevc_pps *pps = run->pps;
> + const struct v4l2_ctrl_hevc_slice_params *sl;
> + struct avd_hevc_tile_info *tile_info = &run->tile_info;
> + bool tiles_enabled, first_slice, first_segment;
> + bool hflip, vflip;
> + int slice_segment_offset, entry_point_idx = 0, pos = 0, offset = 0;
> + int row, col, i, s, to;
> + int slice_flag, size, new_offset;
> + int tile_id, last_tile_id;
> + u16 log2_min_cb_size, width, height;
> + u32 max_cu_width, pic_in_ctbs_width, pic_in_ctbs_height;
> + u32 pic_in_ctbs_size, num_cols, last_tile_block = 0;
> + struct sl_ctx last = {
> + .q1_col = -1,
> + .q1_row = -1,
> + };
> +
> + width = sps->pic_width_in_luma_samples;
> + height = sps->pic_height_in_luma_samples;
> +
> + tiles_enabled = !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED);
> +
> + log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3;
> +
> + num_cols = pps->num_tile_columns_minus1 + 1;
> +
> + max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size +
> + log2_min_cb_size);
> + pic_in_ctbs_width = (width + max_cu_width - 1) / max_cu_width;
> + pic_in_ctbs_height = (height + max_cu_width - 1) / max_cu_width;
> + pic_in_ctbs_size = pic_in_ctbs_width * pic_in_ctbs_height;
> +
> + for (s = 0; s < run->num_slices; s++) {
> + sl = &(*run->sl)[s];
> + slice_segment_offset = 0;
> +
> + /* entry_points are not needed for WPP */
> + to = tiles_enabled ? sl->num_entry_point_offsets + 1 : 1;
> +
> + for (i = 0; i < to; i++) {
> + first_segment = i == 0;
> + first_slice = s == 0;
> +
> + if (tiles_enabled && to > 1) {
> + if (i < sl->num_entry_point_offsets) {
> + size = (*run->entry_point_offsets)
> + [entry_point_idx++];
[Severity: High]
Is it safe to locklessly dereference the V4L2 dynamic array pointer here?
In avd_hevc_run_preamble(), the pointer to the dynamic array control is cached
without acquiring the V4L2 state lock:
run->entry_point_offsets = ctrl ? (const u32 **)&ctrl->p_cur.p : NULL;
...
run->sl = ctrl ?
(const struct v4l2_ctrl_hevc_slice_params **)&ctrl->p_cur.p
: NULL;
If a concurrent VIDIOC_S_EXT_CTRLS ioctl reallocates the array to a smaller size
while the decoder is running, *run->entry_point_offsets will point to the new
smaller array, but the cached bounds will remain large, leading to out-of-bounds
heap reads here. Also, dereferencing p_cur.p concurrently while it is being freed
could trigger a Use-After-Free.
[Severity: High]
Does this code allow an integer overflow during validation?
In avd_hevc_run_preamble(), the total number of entry points is accumulated
into a signed 32-bit int:
for (i = 0; i < run->num_slices; i++)
sum += (*run->sl)[i].num_entry_point_offsets;
If userspace provides extremely large num_entry_point_offsets, could this
sum wrap around and exactly match run->num_entry_point_offsets to bypass the
validation? If so, entry_point_idx could iterate out-of-bounds and read past
the end of the allocated entry_point_offsets array here.
> + new_offset = size;
> + } else {
> + size = sl->bit_size / 8 -
> + sl->data_byte_offset -
> + slice_segment_offset;
> + new_offset =
> + size + sl->data_byte_offset;
> + }
> + } else {
> + size = (sl->bit_size) / 8 -
> + sl->data_byte_offset;
> + new_offset = size + sl->data_byte_offset;
[Severity: High]
Can the subtraction here trigger an integer underflow?
Userspace could supply sl->data_byte_offset or entry point offsets such that
they exceed sl->bit_size / 8. This would cause an underflow, resulting in a
huge integer for 'size'.
When set_slice() is called:
dma_addr_t coded_in =
run->base.coded_in + offset + sl->data_byte_offset;
this uncontrolled size, alongside an unvalidated memory address, is programmed
into the hardware, potentially causing it to perform arbitrary DMA memory reads.
> + }
> +
> + if (sl->slice_segment_addr > pic_in_ctbs_size)
> + return;
> +
> + tile_id = tile_info->tile_ids
> + [tile_info->ctb_addr_rs_to_ts
> + [sl->slice_segment_addr]];
[Severity: High]
Does this validation allow an off-by-one error?
Because it checks for > rather than >=, could sl->slice_segment_addr exactly
equal pic_in_ctbs_size, leading to a heap out-of-bounds read of
ctb_addr_rs_to_ts? The resulting garbage value might then be used to read
tile_ids out-of-bounds.
> + last_tile_id =
[ ... ]
> +static int avd_hevc_compute_tiles(struct avd_ctx *ctx, struct avd_hevc_run *run)
> +{
> + const struct v4l2_ctrl_hevc_sps *sps = run->sps;
> + const struct v4l2_ctrl_hevc_pps *pps = run->pps;
> + bool tiles_enabled;
> + struct avd_hevc_tile_info *tile_info = &run->tile_info;
> + u16 log2_min_cb_size, width, height;
> + u32 max_cu_width, pic_in_ctbs_width, pic_in_ctbs_height,
> + pic_in_ctbs_size;
> +
> + width = sps->pic_width_in_luma_samples;
> + height = sps->pic_height_in_luma_samples;
> +
> + log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3;
> +
> + max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size +
> + log2_min_cb_size);
> + pic_in_ctbs_width = (width + max_cu_width - 1) / max_cu_width;
[Severity: High]
Does this code permit an unbounded bit shift?
Userspace provides sps->log2_diff_max_min_luma_coding_block_size and
sps->log2_min_luma_coding_block_size_minus3 without bounds checking. If their
sum exceeds 31, the shift will result in undefined behavior. This could yield 0,
triggering a zero division on the very next line.
Alternatively, if it evaluates to a huge value (like 1 << 30), could this
overflow the 32-bit math during scratch allocations in avd_hevc_alloc_scratch()
like so:
ret = avd_buf_alloc(avd, &hevc_ctx->bufs.ip_above,
((max_col * max_cu_width) / 4) * bit_depth);
causing it to wrap around to a dangerously small size, leading to hardware
memory corruption?
> + pic_in_ctbs_height = (height + max_cu_width - 1) / max_cu_width;
> + pic_in_ctbs_size = pic_in_ctbs_height * pic_in_ctbs_width;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260926-avd-v2-0-ecebe6a3648e@icloud.com?part=9
next prev parent reply other threads:[~2026-09-26 13:35 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-26 13:14 [PATCH v2 00/17] media: apple: add avd driver Sofus Forstreuter
2026-09-26 13:14 ` [PATCH v2 01/17] dt-bindings: media: add apple,avd Sofus Forstreuter
2026-09-29 8:46 ` Krzysztof Kozlowski
2026-09-26 13:14 ` [PATCH v2 02/17] media: v4l2: Add P210 pixel format Sofus Forstreuter
2026-09-26 13:14 ` [PATCH v2 03/17] media: v4l2: Add Apple interchange pixel formats Sofus Forstreuter
2026-09-26 13:14 ` [PATCH v2 04/17] media: v4l2-ctrls: validate av1 tile info Sofus Forstreuter
2026-09-26 13:29 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 05/17] media: v4l2-ctrls: validate vp9 tile_rows_log2 Sofus Forstreuter
2026-09-26 13:14 ` [PATCH v2 06/17] media: apple: add avd driver Sofus Forstreuter
2026-09-26 13:36 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 07/17] media: apple: avd: add h264 support Sofus Forstreuter
2026-09-26 13:34 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 08/17] media: apple: avd: add vp9 support Sofus Forstreuter
2026-09-26 13:33 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 09/17] media: apple: avd: add hevc support Sofus Forstreuter
2026-09-26 13:35 ` sashiko-bot [this message]
2026-09-26 13:14 ` [PATCH v2 10/17] media: apple: avd: add av1 support Sofus Forstreuter
2026-09-26 13:37 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 11/17] arm64: dts: apple: t8103: add avd nodes Sofus Forstreuter
2026-09-26 13:14 ` [PATCH v2 12/17] arm64: dts: apple: t8112: " Sofus Forstreuter
2026-09-26 13:35 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 13/17] arm64: dts: apple: t8122: " Sofus Forstreuter
2026-09-26 13:29 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 14/17] arm64: dts: apple: t600x: " Sofus Forstreuter
2026-09-26 13:14 ` [PATCH v2 15/17] arm64: dts: apple: t602x: " Sofus Forstreuter
2026-09-26 13:14 ` [PATCH v2 16/17] arm64: dts: apple: t6030: " Sofus Forstreuter
2026-09-26 13:34 ` sashiko-bot
2026-09-26 13:14 ` [PATCH v2 17/17] arm64: dts: apple: t6031: " Sofus Forstreuter
2026-09-26 13:35 ` sashiko-bot
2026-10-05 22:29 ` [PATCH v2 00/17] media: apple: add avd driver Neal Gompa
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260926133503.276A71F000FF@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=media-ci@linuxtv.org \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=sofus.c@icloud.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox