Wayland capture: validate buffer geometry before pixel copy

Reject Memfd buffers where stride exceeds the mapped region to prevent
out-of-bounds reads. Cap stream dimensions to prevent integer overflow
in frame allocation. Bound cursor bitmap stride to reject oversized
values.

Bug: chromium:509294495
Change-Id: I524365383d4d68896377b1fe95e465c56e22a699
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/469682
Reviewed-by: Andreas Pehrson <apehrson@mozilla.com>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Commit-Queue: Jan Grulich <grulja@gmail.com>
Cr-Commit-Position: refs/heads/main@{#47627}
diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
index 0bced6d..0a15c48 100644
--- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
+++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
@@ -65,6 +65,9 @@
 constexpr int kBytesPerPixel = 4;
 constexpr int kMaxCursorSize = 1024;
 constexpr int kVideoDamageRegionCount = 16;
+// A reasonable maximum size so "width * height * kBytesPerPixel" doesn't
+// overflow
+constexpr int kMaxScreenCastDimension = 16384;
 
 constexpr int CursorMetaSize(int w, int h) {
   return (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) +
@@ -319,6 +322,12 @@
       has_modifier ? that->spa_video_format_.modifier : DRM_FORMAT_MOD_INVALID;
   that->stream_size_ = DesktopSize(that->spa_video_format_.size.width,
                                    that->spa_video_format_.size.height);
+  if (that->stream_size_.is_empty() ||
+      that->stream_size_.width() > kMaxScreenCastDimension ||
+      that->stream_size_.height() > kMaxScreenCastDimension) {
+    that->stream_size_ = DesktopSize();
+    return;
+  }
 
   if (that->observer_) {
     that->observer_->OnFormatChanged(
@@ -768,7 +777,11 @@
 
         if (bitmap && bitmap->size.width > 0 &&
             bitmap->size.width <= kMaxCursorSize && bitmap->size.height > 0 &&
-            bitmap->size.height <= kMaxCursorSize) {
+            bitmap->size.height <= kMaxCursorSize &&
+            bitmap->stride >=
+                static_cast<int32_t>(bitmap->size.width * kBytesPerPixel) &&
+            bitmap->stride * bitmap->size.height <=
+                kMaxCursorSize * kMaxCursorSize * kBytesPerPixel) {
           const uint8_t* bitmap_data =
               SPA_MEMBER(bitmap, bitmap->offset, uint8_t);
           // TODO(bugs.webrtc.org/436974448): Convert `spa_video_format` to
@@ -993,29 +1006,38 @@
   ScopedBuf map;
   uint8_t* src = nullptr;
 
+  const uint64_t maxsize = static_cast<uint64_t>(spa_buffer->datas[0].maxsize);
+  const uint64_t mapoffset =
+      static_cast<uint64_t>(spa_buffer->datas[0].mapoffset);
+
   map.initialize(
-      static_cast<uint8_t*>(
-          mmap(nullptr,
-               spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
-               PROT_READ, MAP_PRIVATE, spa_buffer->datas[0].fd, 0)),
-      spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset,
-      spa_buffer->datas[0].fd);
+      static_cast<uint8_t*>(mmap(nullptr, maxsize + mapoffset, PROT_READ,
+                                 MAP_PRIVATE, spa_buffer->datas[0].fd, 0)),
+      maxsize + mapoffset, spa_buffer->datas[0].fd);
 
   if (!map) {
     RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " << std::strerror(errno);
     return false;
   }
 
-  src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t);
+  src = SPA_MEMBER(map.get(), mapoffset, uint8_t);
 
-  uint32_t buffer_stride = spa_buffer->datas[0].chunk->stride;
-  uint32_t src_stride = buffer_stride;
+  const uint64_t src_stride = spa_buffer->datas[0].chunk->stride;
+
+  if (src_stride > INT32_MAX ||
+      src_stride < static_cast<uint64_t>(offset.x() + frame.size().width()) *
+                       kBytesPerPixel ||
+      src_stride * (offset.y() + frame.size().height()) > maxsize) {
+    RTC_LOG(LS_ERROR) << "Rejecting MemFd buffer with invalid geometry: stride="
+                      << src_stride << " maxsize=" << maxsize;
+    return false;
+  }
 
   uint8_t* updated_src =
       src + (src_stride * offset.y()) + (kBytesPerPixel * offset.x());
 
   frame.CopyPixelsFrom(
-      updated_src, (src_stride - (kBytesPerPixel * offset.x())),
+      updated_src, static_cast<int>(src_stride),
       DesktopRect::MakeWH(frame.size().width(), frame.size().height()));
 
   return true;
diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc
index d51305b..45f4aa3 100644
--- a/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc
+++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream_unittest.cc
@@ -395,6 +395,17 @@
       blue_color, TestScreenCastStreamProvider::EmptyData);
   emptyFrameEvent.Wait(kShortWait);
 
+  // Check that a MemFd buffer with an invalid stride is rejected
+  Event invalidStrideEvent;
+  EXPECT_CALL(*this, OnFrameRecorded);
+  EXPECT_CALL(*this, OnFailedToProcessBuffer).WillOnce([&invalidStrideEvent] {
+    invalidStrideEvent.Set();
+  });
+
+  test_screencast_stream_provider_->RecordFrame(
+      blue_color, TestScreenCastStreamProvider::InvalidStride);
+  invalidStrideEvent.Wait(kShortWait);
+
   // Test disconnection from stream
   EXPECT_CALL(*this, OnStopStreaming);
   shared_screencast_stream_->StopScreenCastStream();
diff --git a/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc b/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc
index e1eaa36..1478031 100644
--- a/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc
+++ b/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.cc
@@ -235,6 +235,8 @@
     spa_data->chunk->size = 0;
   } else if (frame_defect == CorruptedData) {
     spa_data->chunk->flags = SPA_CHUNK_FLAG_CORRUPTED;
+  } else if (frame_defect == InvalidStride) {
+    spa_data->chunk->stride = spa_data->maxsize + 1;
   } else if (frame_defect == CorruptedMetadata) {
     struct spa_meta_header* spa_header =
         static_cast<spa_meta_header*>(spa_buffer_find_meta_data(
diff --git a/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h b/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h
index 2262d49..82feb90 100644
--- a/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h
+++ b/modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h
@@ -41,7 +41,13 @@
     virtual ~Observer() = default;
   };
 
-  enum FrameDefect { None, EmptyData, CorruptedData, CorruptedMetadata };
+  enum FrameDefect {
+    None,
+    EmptyData,
+    CorruptedData,
+    CorruptedMetadata,
+    InvalidStride
+  };
 
   explicit TestScreenCastStreamProvider(Observer* observer,
                                         uint32_t width,