[PATCH v2] media: mtk-vcodec: vdec_vp8_if: guard list_first_entry on empty fb node pool

From: Li Ximo

Date: Wed Oct 28 2026 - 00:22:26 EST


From: Wang Haoran <haoranwangsec@xxxxxxxxx>

list_first_entry() does not return NULL on an empty list; it returns a
wild pointer computed by container_of() on the list head, and
dereferencing it corrupts memory. vdec_vp8_if.c has three unguarded
list_first_entry() calls on inst->available_fb_node_list: two in
vp8_dec_finish() (one unconditional, one on the show_frame path) and
one in add_fb_to_free_list(). The pool holds a fixed number of nodes
(VP8_MAX_FRM_BUF_NODE_NUM = 10) prefilled by init_list(), nodes leave
the pool on every successful decode (two per show_frame decode and via
the vpu error / resolution-change paths), and they are returned to the
pool only through the userspace-driven get_disp_fb()/get_free_fb()
calls -- which themselves use list_first_entry_or_null(), i.e. the
driver already treats the pool as exhaustible on the return path but
not on the take paths. If the VPU firmware keeps reporting a stale or
zero prev_y_dma, or if userspace decodes more frames than it
reclaims, the pool is exhausted and the next list_first_entry()
returns a wild pointer into struct vdec_vp8_inst, after which
node->fb = ... and list_move_tail() perform an in-structure
out-of-bounds write and list corruption. This was found by code
audit; we do not have a reproducer on real hardware.

Fix this by switching the three take sites to
list_first_entry_or_null() and bailing out when the pool is empty,
matching the existing style of get_disp_fb()/get_free_fb() in the same
file. On exhaustion vp8_dec_finish() now logs an error and stops
tracking the frame instead of corrupting memory, and
add_fb_to_free_list() simply drops the buffer.

Reported-by: Wang Haoran <haoranwangsec@xxxxxxxxx>
Link: https://lore.kernel.org/r/20261023075633.3662574-1-lixicaicai@xxxxxxxxx
Reported-by: Li Ximo <lixicaicai@xxxxxxxxx>
Link: https://lore.kernel.org/r/20261023075633.3662574-1-lixicaicai@xxxxxxxxx
Signed-off-by: Wang Haoran <haoranwangsec@xxxxxxxxx>
---
v2: add Link: tag referencing the v1 submission as requested by the
media CI; no code change.

.../vcodec/decoder/vdec/vdec_vp8_if.c | 22 ++++++++++++++-----
1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp8_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp8_if.c
index fd15d462b..ba34c71f7 100644
--- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp8_if.c
+++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp8_if.c
@@ -308,15 +308,23 @@ static void vp8_dec_finish(struct vdec_vp8_inst *inst)
}

/* available_fb_node_list -> fb_use_list */
- node = list_first_entry(&inst->available_fb_node_list,
- struct vdec_fb_node, list);
+ node = list_first_entry_or_null(&inst->available_fb_node_list,
+ struct vdec_fb_node, list);
+ if (!node) {
+ mtk_vdec_err(inst->ctx, "no available fb node");
+ return;
+ }
node->fb = inst->cur_fb;
list_move_tail(&node->list, &inst->fb_use_list);

/* available_fb_node_list -> fb_disp_list */
if (inst->vsi->dec.show_frame) {
- node = list_first_entry(&inst->available_fb_node_list,
- struct vdec_fb_node, list);
+ node = list_first_entry_or_null(&inst->available_fb_node_list,
+ struct vdec_fb_node, list);
+ if (!node) {
+ mtk_vdec_err(inst->ctx, "no available fb node");
+ return;
+ }
node->fb = inst->cur_fb;
list_move_tail(&node->list, &inst->fb_disp_list);
}
@@ -352,8 +360,10 @@ static void add_fb_to_free_list(struct vdec_vp8_inst *inst, void *fb)
struct vdec_fb_node *node;

if (fb) {
- node = list_first_entry(&inst->available_fb_node_list,
- struct vdec_fb_node, list);
+ node = list_first_entry_or_null(&inst->available_fb_node_list,
+ struct vdec_fb_node, list);
+ if (!node)
+ return;
node->fb = fb;
list_move_tail(&node->list, &inst->fb_free_list);
}
--
2.30.2