mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 0/3] media: uvcvideo: harden the frame buffer size computation
@ 2026-10-27 11:11 Natasha Klaus
  2026-10-27 11:11 ` [PATCH v4 1/3] media: uvcvideo: Let uvc_parse_frame() report a skipped frame Natasha Klaus
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Natasha Klaus @ 2026-10-27 11:11 UTC (permalink / raw)
  To: laurent.pinchart, hansg, mchehab
  Cc: ribalda, noambs2999, david.laight.linux, linux-media,
	linux-kernel, Natasha Klaus

uvc_parse_frame() computes dwMaxVideoFrameBufferSize as
bpp * wWidth * wHeight / 8 in int, from unvalidated descriptor
fields. The product wraps; the stored size is wrong and can be zero.

1/3 lets uvc_parse_frame() skip one frame descriptor instead of
failing the whole streaming interface (Ricardo's suggestion).
2/3 is Noam's overflow fix, adapted to skip.
3/3 skips a frame whose computed size is zero.

Stable: 1/3 carries Cc: stable without Fixes: because 2/3 needs it.
Both or neither.

Testing: Noam, UVC gadget over dummy_hcd, on v3 code; the >> 3 in
2/3 does not change the result for his inputs. No physical camera.
-ENODATA path not exercised. Builds W=1 on x86_64 and i386
allmodconfig.

Tools: prepared with Claude Code. It applied Ricardo's design in 1/3
and fixed the build, wrote the code and comment in 3/3 and found that
case, drafted the commit messages and notes from my outline, and did
the call path analysis. I reviewed and built everything. 2/3 is
Noam's; he found the overflow with Claude's help and wrote the fix by
hand (confirmed off-list).

v3: https://lore.kernel.org/linux-media/20260820111556.232652-1-natalie.klaus@runtimeverification.com/

Changes in v4:
- 2/3: >> 3 instead of DIV_ROUND_UP_ULL. Per Laurent on v3 2/3, width * bpp
  is a multiple of 8 on real devices. Keeps the stable fix minimal.
- 2/3 and 3/3 changelogs say how each case was found.
- No Assisted-by tag: removed from the docs in 1e035297ffda. Tool use
  is in this cover letter instead.
- Rebased onto d135ab453a74.

Changes in v3:
- Tested-by from Noam on all three.

Changes in v2:
- 1/3: check -ENODATA before counting the frame (Ricardo).
- 2/3: DIV_ROUND_UP -> DIV_ROUND_UP_ULL (Ricardo).
- Reviewed-by from Ricardo on all three.

Natasha Klaus (2):
  media: uvcvideo: Let uvc_parse_frame() report a skipped frame
  media: uvcvideo: Skip frame descriptors with a zero computed size

Noam Ben Shimon (1):
  media: uvcvideo: Fix integer overflow in frame buffer size calculation

 drivers/media/usb/uvc/uvc_driver.c | 52 ++++++++++++++++++++++--------
 1 file changed, 39 insertions(+), 13 deletions(-)


base-commit: d135ab453a7400a34930bdda1ef4bdc8acfac80e
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v4 1/3] media: uvcvideo: Let uvc_parse_frame() report a skipped frame
  2026-10-27 11:11 [PATCH v4 0/3] media: uvcvideo: harden the frame buffer size computation Natasha Klaus
@ 2026-10-27 11:11 ` Natasha Klaus
  2026-10-27 11:11 ` [PATCH v4 2/3] media: uvcvideo: Fix integer overflow in frame buffer size calculation Natasha Klaus
  2026-10-27 11:11 ` [PATCH v4 3/3] media: uvcvideo: Skip frame descriptors with a zero computed size Natasha Klaus
  2 siblings, 0 replies; 4+ messages in thread
From: Natasha Klaus @ 2026-10-27 11:11 UTC (permalink / raw)
  To: laurent.pinchart, hansg, mchehab
  Cc: ribalda, noambs2999, david.laight.linux, linux-media,
	linux-kernel, Natasha Klaus, stable

uvc_parse_frame() returns the descriptor length on success and a
negative error code on failure, and uvc_parse_format() treats every
negative value as fatal for the whole streaming interface. There is no
way for the parser to say "this frame descriptor is unusable, but the
rest of the format is fine".

Change the return convention so it can. Return 0 on success and let the
caller advance by buffer[0], which is the value the function returned
anyway. Report a truncated descriptor with -ENODATA, which stays fatal,
and leave every other negative value to mean "skip this frame descriptor
and carry on with the next one".

-ENODATA is currently the only error the function can return, so the
skip path is unreachable until later patches add checks that use it. The
one behavioural change is the truncated-descriptor diagnostic, which
moves from uvc_dbg() to dev_warn() so a malformed descriptor is reported
without the DESCR debug flag. That leaves the local alts variable
unused, and the kernel builds -Wunused-variable as an error, so it goes
too.

Suggested-by: Ricardo Ribalda <ribalda@chromium.org>
Link: https://lore.kernel.org/linux-media/CANiDSCue8yyiGubzbAybRqSUTTFuB=-Y2TZy6yvOx32SpAASWg@mail.gmail.com/
Cc: stable@vger.kernel.org
Reviewed-by: Ricardo Ribalda <ribalda@chromium.org>
Tested-by: Noam Ben Shimon <noambs2999@gmail.com>
Signed-off-by: Natasha Klaus <natalie.klaus@runtimeverification.com>
---
Changes in v4: rebased onto d135ab453a74.

stable: no Fixes: on purpose, 2/3 needs this patch. Both or
neither.

 drivers/media/usb/uvc/uvc_driver.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index cbc14569716b..14ab4cc28c40 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -230,7 +230,6 @@ static int uvc_parse_frame(struct uvc_device *dev,
 			   u32 **intervals, u8 ftype, int width_multiplier,
 			   const unsigned char *buffer, int buflen)
 {
-	struct usb_host_interface *alts = streaming->intf->cur_altsetting;
 	unsigned int maxIntervalIndex;
 	unsigned int interval;
 	unsigned int i, n;
@@ -243,10 +242,10 @@ static int uvc_parse_frame(struct uvc_device *dev,
 	n = n ? n : 3;
 
 	if (buflen < 26 + 4 * n) {
-		uvc_dbg(dev, DESCR,
-			"device %d videostreaming interface %d FRAME error\n",
-			dev->udev->devnum, alts->desc.bInterfaceNumber);
-		return -EINVAL;
+		dev_warn(&streaming->intf->dev,
+			 "UVC non compliance: FRAME descriptor is %d bytes, expected at least %u.\n",
+			 buflen, 26 + 4 * n);
+		return -ENODATA;
 	}
 
 	frame->bFrameIndex = buffer[3];
@@ -329,7 +328,7 @@ static int uvc_parse_frame(struct uvc_device *dev,
 
 	*intervals += n;
 
-	return buffer[0];
+	return 0;
 }
 
 static int uvc_parse_format(struct uvc_device *dev,
@@ -492,11 +491,12 @@ static int uvc_parse_format(struct uvc_device *dev,
 			ret = uvc_parse_frame(dev, streaming, format, frame,
 					      intervals, ftype, width_multiplier,
 					      buffer, buflen);
-			if (ret < 0)
+			if (ret == -ENODATA)
 				return ret;
-			format->nframes++;
-			buflen -= ret;
-			buffer += ret;
+			if (!ret)
+				format->nframes++;
+			buflen -= buffer[0];
+			buffer += buffer[0];
 		}
 	}
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v4 2/3] media: uvcvideo: Fix integer overflow in frame buffer size calculation
  2026-10-27 11:11 [PATCH v4 0/3] media: uvcvideo: harden the frame buffer size computation Natasha Klaus
  2026-10-27 11:11 ` [PATCH v4 1/3] media: uvcvideo: Let uvc_parse_frame() report a skipped frame Natasha Klaus
@ 2026-10-27 11:11 ` Natasha Klaus
  2026-10-27 11:11 ` [PATCH v4 3/3] media: uvcvideo: Skip frame descriptors with a zero computed size Natasha Klaus
  2 siblings, 0 replies; 4+ messages in thread
From: Natasha Klaus @ 2026-10-27 11:11 UTC (permalink / raw)
  To: laurent.pinchart, hansg, mchehab
  Cc: ribalda, noambs2999, david.laight.linux, linux-media,
	linux-kernel, stable, Natasha Klaus

From: Noam Ben Shimon <noambs2999@gmail.com>

In the function uvc_parse_frame(), it recomputes
dwMaxVideoFrameBufferSize for uncompressed formats. This helps working
around devices that report it wrong:

	frame->dwMaxVideoFrameBufferSize = format->bpp * frame->wWidth
					 * frame->wHeight / 8;

These three arguments originate from the device's own descriptors, and
therefore can be decided by it. bpp is a u8 and wWidth and wHeight are
u16. The expression is evaluated in int, and the maximum value is
255 * 65535 * 65535 (which is roughly 510 times INT_MAX).
A device that declares large dimensions therefore overflows a signed
int here.
The kernel is built using -fno-strict-overflow, so this wraps rather
than being miscompiled, but the wrapped value (which is often negative)
is then divided by 8 and stored in a u32 used as a size.

Two examples for this (using legal field values):

  - 32 bpp, 16384x4096: the product is exactly 2^31 and wraps to
    INT_MIN. After division and conversion to u32 the field has the
    value 4026531840 rather than 268435456.

  - 16 bpp, 16384x16384: the product is exactly 2^32 and wraps to 0.
    The field holds 0, rather than the correct 536870912.

I don't think memory corruption is a consequence of this. The value
reaches uvc_queue_setup() as the vb2 buffer size, and every copy on the
decode path is bounded by buf->length, which uvc_buffer_prepare() gets
from vb2_plane_size() rather than this field. What a wrapped value does
instead is make the driver describe the stream inconsistently.
For an uncompressed format uvc_fixup_video_ctrl() copies it into
ctrl->dwMaxVideoFrameSize unconditionally, and that becomes the
sizeimage reported by VIDIOC_G_FMT. This is while width, height and
bytesperline continue to describe the full frame.
This also makes uvc_video_validate_buffer() mark error on all frames,
because it is comparing bytesused against the same number.

Compute the size in 64-bit, and if the result does not fit in the u32
field then skip the frame descriptor. An uncompressed frame this large
is probably not a real device, and skipping it leaves the rest of the
format and the streaming interface usable.

The overflow was found with the help of Claude. The fix itself
was written by hand.

Fixes: c0efd232929c ("V4L/DVB (8145a): USB Video Class driver")
Cc: stable@vger.kernel.org
Signed-off-by: Noam Ben Shimon <noambs2999@gmail.com>
Reviewed-by: Ricardo Ribalda <ribalda@chromium.org>
Tested-by: Noam Ben Shimon <noambs2999@gmail.com>
Signed-off-by: Natasha Klaus <natalie.klaus@runtimeverification.com>
---
Changes in v4:
- >> 3 instead of DIV_ROUND_UP_ULL, see cover letter. This is the
  form Ricardo reviewed on Noam's v2; Noam's test inputs give the
  same result, so Reviewed-by and Tested-by are kept.
- Changelog says how the overflow was found.
- Rebased onto d135ab453a74.

Changes from Noam's v2: rebased on 1/3; dev_warn() instead of
uvc_dbg(); operand values in the message (David Laight); last
paragraph describes skipping instead of rejecting.

 drivers/media/usb/uvc/uvc_driver.c | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index 14ab4cc28c40..4d1380c38ec6 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -295,9 +295,21 @@ static int uvc_parse_frame(struct uvc_device *dev,
 	 * information. For uncompressed formats this can be fixed by computing
 	 * the value from the frame size.
 	 */
-	if (!(format->flags & UVC_FMT_FLAG_COMPRESSED))
-		frame->dwMaxVideoFrameBufferSize = format->bpp * frame->wWidth
-						 * frame->wHeight / 8;
+	if (!(format->flags & UVC_FMT_FLAG_COMPRESSED)) {
+		u64 bufsize;
+
+		bufsize = ((u64)format->bpp * frame->wWidth *
+			   frame->wHeight) >> 3;
+		if (bufsize > U32_MAX) {
+			dev_warn(&streaming->intf->dev,
+				 "UVC non compliance: FRAME %u computed buffer size overflows (%ux%u, %u bpp), skipping it.\n",
+				 frame->bFrameIndex, frame->wWidth,
+				 frame->wHeight, format->bpp);
+			return -EINVAL;
+		}
+
+		frame->dwMaxVideoFrameBufferSize = bufsize;
+	}
 
 	/*
 	 * Clamp the default frame interval to the boundaries. A zero
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v4 3/3] media: uvcvideo: Skip frame descriptors with a zero computed size
  2026-10-27 11:11 [PATCH v4 0/3] media: uvcvideo: harden the frame buffer size computation Natasha Klaus
  2026-10-27 11:11 ` [PATCH v4 1/3] media: uvcvideo: Let uvc_parse_frame() report a skipped frame Natasha Klaus
  2026-10-27 11:11 ` [PATCH v4 2/3] media: uvcvideo: Fix integer overflow in frame buffer size calculation Natasha Klaus
@ 2026-10-27 11:11 ` Natasha Klaus
  2 siblings, 0 replies; 4+ messages in thread
From: Natasha Klaus @ 2026-10-27 11:11 UTC (permalink / raw)
  To: laurent.pinchart, hansg, mchehab
  Cc: ribalda, noambs2999, david.laight.linux, linux-media,
	linux-kernel, Natasha Klaus

For uncompressed formats uvc_parse_frame() recomputes
dwMaxVideoFrameBufferSize from the frame dimensions and the bits per
pixel. All three operands are read straight from the descriptor bytes
with no range check, so the computed size is zero whenever any of them
is zero.

A zero size is not harmless. It is copied into
ctrl->dwMaxVideoFrameSize by uvc_fixup_video_ctrl() and reaches
uvc_queue_setup() as the vb2 plane size, where it trips
WARN_ON(!plane_sizes[i]) in vb2_core_reqbufs() at
drivers/media/common/videobuf2/videobuf2-core.c:951 and fails
VIDIOC_REQBUFS with -EINVAL. On a kernel built with panic_on_warn that
WARN is fatal.

Such a frame can also become the active one without any application
asking for it: when no frame matches the device's default bFrameIndex,
uvc_video_init() falls back to frames[0], so a device that also has
usable frames can come up unusable.

Skip the frame rather than rejecting the descriptor, which would discard
the whole streaming interface and every valid format on it. This follows
commit 81f3affa19d6 ("media: uvcvideo: Don't expose unsupported formats
to userspace"), which drops a format descriptor the driver cannot use
for the same reason: to keep it from reaching userspace and triggering a
WARN_ON.

The zero size case was found with Claude Code, an LLM tool, while
reviewing Noam's overflow patch.

Reviewed-by: Ricardo Ribalda <ribalda@chromium.org>
Tested-by: Noam Ben Shimon <noambs2999@gmail.com>
Signed-off-by: Natasha Klaus <natalie.klaus@runtimeverification.com>
---
Changes in v4:
- Changelog says how the zero size case was found.
- Rebased onto d135ab453a74.

Needs 1/3 for -EINVAL to mean "skip" and 2/3 for bufsize.

 drivers/media/usb/uvc/uvc_driver.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index 4d1380c38ec6..c1c05bba7f7f 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -308,6 +308,20 @@ static int uvc_parse_frame(struct uvc_device *dev,
 			return -EINVAL;
 		}
 
+		/*
+		 * A zero-sized frame is unusable: it reaches vb2 as a zero
+		 * plane size, and it is reported to userspace as a 0x0 frame
+		 * with a zero sizeimage. Skip the frame descriptor, the
+		 * caller moves on to the next one.
+		 */
+		if (!bufsize) {
+			dev_warn(&streaming->intf->dev,
+				 "UVC non compliance: FRAME %u has zero size (%ux%u, %u bpp), skipping it.\n",
+				 frame->bFrameIndex, frame->wWidth,
+				 frame->wHeight, format->bpp);
+			return -EINVAL;
+		}
+
 		frame->dwMaxVideoFrameBufferSize = bufsize;
 	}
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-10-27 11:11 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 11:11 [PATCH v4 0/3] media: uvcvideo: harden the frame buffer size computation Natasha Klaus
2026-10-27 11:11 ` [PATCH v4 1/3] media: uvcvideo: Let uvc_parse_frame() report a skipped frame Natasha Klaus
2026-10-27 11:11 ` [PATCH v4 2/3] media: uvcvideo: Fix integer overflow in frame buffer size calculation Natasha Klaus
2026-10-27 11:11 ` [PATCH v4 3/3] media: uvcvideo: Skip frame descriptors with a zero computed size Natasha Klaus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®