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,