[PATCH] media: vivid: round the height down to the vertical subsampling factor
From: Maxim Skokov
Date: Sat Sep 05 2026 - 12:02:30 EST
syzbot reports a vmalloc out-of-bounds write in the test pattern
generator:
BUG: KASAN: vmalloc-out-of-bounds in tpg_fill_plane_pattern drivers/media/common/v4l2-tpg/v4l2-tpg-core.c:2617 [inline]
BUG: KASAN: vmalloc-out-of-bounds in tpg_fill_plane_buffer+0x2063/0x4160 drivers/media/common/v4l2-tpg/v4l2-tpg-core.c:2705
Write of size 720 at addr ffffc900038f9d50 by task vivid-000-vid-c/6017
The reproducer requests a 720x49 NV12 capture format, i.e. an odd
height for a format whose chroma plane is vertically subsampled.
The buffer size is derived from the height by a truncating division:
sizes[p] = (tpg_g_line_width(&dev->tpg, p) * h) /
dev->fmt_cap->vdownsampling[p] +
dev->fmt_cap->data_offset[p];
For a single buffer holding both planes tpg_g_line_width() returns
720 + 720 / 2 = 1080, so 1080 * 49 = 52920 bytes get allocated.
tpg_fill_plane_buffer() however emits one chroma line for every two
luma lines, i.e. DIV_ROUND_UP(49, 2) = 25 lines, and thus needs
49 * 720 + 25 * 720 = 53280 bytes. The memcpy() of the last chroma
line runs 360 bytes past the end of the buffer.
An odd height is not meaningful for a 4:2:0 format in the first place,
since the chroma plane would have to hold half a line. Rather than
fixing up each of the ~10 sites that divide the height by
vdownsampling[], round the height down to a multiple of the vertical
subsampling factor where it enters the driver. Adjusting the format is
what TRY_FMT/S_FMT are for, and it keeps every later division exact.
Formats without vertical subsampling are unaffected and keep accepting
odd heights.
Tested with the syzbot reproducer, which no longer triggers the splat,
and by streaming NV12, NV21, YUV420, YVU420 and YUYV at heights 47,
48, 49, 51, 480, 481 and 1081. v4l2-compliance gives identical results
before and after (48 of 50 succeeded on the vivid device in both
cases; the two failures are pre-existing and unrelated).
Fixes: ddcaee9dd4c0 ("[media] vivid: add support for single buffer planar formats")
Reported-by: syzbot+cb43e758a4dc84dd467f@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=cb43e758a4dc84dd467f
Signed-off-by: Maxim Skokov <skokovmaksimevg@xxxxxxxxx>
---
drivers/media/test-drivers/vivid/vivid-vid-cap.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/media/test-drivers/vivid/vivid-vid-cap.c b/drivers/media/test-drivers/vivid/vivid-vid-cap.c
index e20449084..147af0f9b 100644
--- a/drivers/media/test-drivers/vivid/vivid-vid-cap.c
+++ b/drivers/media/test-drivers/vivid/vivid-vid-cap.c
@@ -570,6 +570,7 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv,
const struct vivid_fmt *fmt;
unsigned bytesperline, max_bpl;
unsigned factor = 1;
+ unsigned int vdiv = 1;
unsigned w, h;
unsigned p;
bool user_set_csc = !!(mp->flags & V4L2_PIX_FMT_FLAG_SET_CSC);
@@ -622,6 +623,18 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv,
mp->height = r.height / factor;
}
+ /*
+ * The chroma planes of vertically subsampled formats hold
+ * height / vdownsampling lines. If the height is not a multiple of
+ * the subsampling factor, then the buffer size calculations round
+ * that number down while the test pattern generator rounds it up,
+ * so the generator writes one line past the end of the buffer.
+ * Round the height down to keep both in sync.
+ */
+ for (p = 0; p < fmt->planes; p++)
+ vdiv = max(vdiv, fmt->vdownsampling[p]);
+ mp->height = rounddown(mp->height, vdiv);
+
/* This driver supports custom bytesperline values */
mp->num_planes = fmt->buffers;
--
2.47.3