Chrome · GPU
CVE-2026-79274
Logic Error in GPU
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/test/data/gpu/webcodecs/copyTo.html |
modified | |
forcontent/test/data/gpu/webcodecs/copyTo.html |
modified | |
ifgpu/command_buffer/client/gles2_implementation.cc |
modified | |
TEST_Fgpu/command_buffer/client/gles2_implementation_unittest.cc |
modified |
Files Changed
content/test/data/gpu/webcodecs/copyTo.htmlgpu/command_buffer/client/gles2_implementation.ccgpu/command_buffer/client/gles2_implementation_unittest.cc
Patch
From b261e1ba70cebb48b58560978af8a5978befe37e Mon Sep 17 00:00:00 2001 From: Kai Ninomiya <[email protected]> Date: Mon, 17 Aug 2026 17:49:52 -0700 Subject: [PATCH] Fix incorrectly copying row padding in command buffer clients VideoFrame::copyTo() is supposed to copy only the pixels and not the padding bytes between rows. Doing so requires one memcpy per row instead of one big memcpy. - RasterImplementation::ReadbackImagePixelsINTERNAL and RasterImplementation::OnAsyncARGBReadbackDone: accessible via VideoFrame::copyTo. - There are many other codepaths reachable from VideoFrame::copyTo(). The others seem to be correct. - RasterImplementation::AsyncYUVReadbackRequest::CopyYUVPlane and GLES2Implementation::ReadbackARGBImagePixelsINTERNAL: also seemed to do this wrong, but I have not looked into exactly how they're used, so I'm not certain they're important or whether they have a security impact. I'm only guessing they are important based on the similar comments that said they write into JS-visible memory (which is how I stumbled on them). Tests: - raster_implementation_unittest tests generated using Gemini, but manually verified that each of the four tests fails when the fix it targets is reverted (e.g. see how the first three were tested in patchset 4: https://crrev.com/c/8255469/4). - Manual drive-by fix some of the initialization sites of MockTransferBuffer::ExpectedMemoryInfo, so that the spanified form can be used instead of raw pointers in the new test. - content/test/data/gpu/webcodecs/copyTo.html test also generated using Gemini, but manually verified that it hits the bug in RasterImplementation::ReadbackImagePixelsINTERNAL. It doesn't seem to hit any of the others on my device (Pixel 3). - WPT test for copyTo() on VideoFrames that are CPU-backed (which was already fine) and texture-backed (which is what this fixes). Unfortunately this test does not seem to catch the issue on CQ, but the texture-backed one does at least catch it locally on my Mac. Fixed: 543707066 Change-Id: Iea2e75cdb75175f250ab5472a22c2f46819e6e84 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8255469 Reviewed-by: Eugene Zemtsov <[email protected]> Commit-Queue: Kai Ninomiya <[email protected]> Cr-Commit-Position: refs/heads/main@{#1680958} --- diff --git a/content/test/data/gpu/webcodecs/copyTo.html b/content/test/data/gpu/webcodecs/copyTo.html index efef2057..5bca638 100644 --- a/content/test/data/gpu/webcodecs/copyTo.html +++ b/content/test/data/gpu/webcodecs/copyTo.html @@ -198,6 +198,78 @@ worker.terminate(); } + // Test that padding bytes (stride/offset padding) are not overwritten. + { + const PAD = 0xAA; + const options = { + layout: [ + {offset: 5, stride: frame.displayWidth * 4 + 16}, // plenty of padding + ], + }; + if (frame.format === 'I420' || frame.format === 'I420A') { + options.layout = [ + {offset: 5, stride: frame.displayWidth + 16}, + {offset: (frame.displayWidth + 16) * frame.displayHeight + 10, stride: frame.displayWidth / 2 + 16}, + {offset: (frame.displayWidth + 16) * frame.displayHeight + (frame.displayWidth / 2 + 16) * (frame.displayHeight / 2) + 20, stride: frame.displayWidth / 2 + 16}, + ]; + if (frame.format === 'I420A') { + options.layout.push({ + offset: (frame.displayWidth + 16) * frame.displayHeight + (frame.displayWidth / 2 + 16) * (frame.displayHeight / 2) * 2 + 30, + stride: frame.displayWidth + 16 + }); + } + } else if (frame.format === 'NV12') { + options.layout = [ + {offset: 5, stride: frame.displayWidth + 16}, + {offset: (frame.displayWidth + 16) * frame.displayHeight + 10, stride: frame.displayWidth + 16}, + ]; + } + + let size_with_padding = frame.allocationSize(options); + let buf = new ArrayBuffer(size_with_padding); + let view = new Uint8Array(buf); + view.fill(PAD); + + let layout = await frame.copyTo(buf, options); + + // Verify that the layout matches what we specified + for (let plane = 0; plane < layout.length; plane++) { + TEST.assert(layout[plane].offset === options.layout[plane].offset, "offset mismatch"); + TEST.assert(layout[plane].stride === options.layout[plane].stride, "stride mismatch"); + } + + // We want to verify that the bytes between rows (the padding bytes) are untouched (still PAD). + for (let plane = 0; plane < layout.length; plane++) { + let plane_offset = layout[plane].offset; + let stride = layout[plane].stride; + let plane_height = frame.displayHeight; + let plane_width = frame.displayWidth; + if (plane > 0 && (frame.format === 'I420' || frame.format === 'I420A' || frame.format === 'NV12')) { + plane_height /= 2; + if (frame.format === 'I420' || frame.format === 'I420A') { + plane_width /= 2; + } + } + let bytes_per_pixel = 1; + if (frame.format === 'RGBA' || frame.format === 'RGBX' || frame.format === 'BGRA' || frame.format === 'BGRX') { + bytes_per_pixel = 4; + } else if (frame.format === 'NV12' && plane === 1) { + bytes_per_pixel = 2; // UV interleaved + } + + let row_bytes = plane_width * bytes_per_pixel; + for (let y = 0; y < plane_height; ++y) { + let row_start = plane_offset + y * stride; + for (let x = row_bytes; x < stride; ++x) { + let pad_idx = row_start + x; + if (pad_idx < size_with_padding) { + TEST.assert(view[pad_idx] === PAD, `Padding byte overwritten at index ${pad_idx} for plane ${plane}, row ${y}, x ${x}. Expected ${PAD}, got ${view[pad_idx]}`); + } + } + } + } + } + // Validate pixels if (!arg.validate_camera_frames && source_type == 'camera') { TEST.log("Skip copyTo result validation"); diff --git a/gpu/command_buffer/client/gles2_implementation.cc b/gpu/command_buffer/client/gles2_implementation.cc index c2ce32c9..fbfc14a 100644 --- a/gpu/command_buffer/client/gles2_implementation.cc +++ b/gpu/command_buffer/client/gles2_implementation.cc @@ -59,6 +59,7 @@ #include "gpu/command_buffer/common/swap_buffers_complete_params.h" #include "gpu/command_buffer/common/sync_token.h" #include "third_party/skia/include/core/SkAlphaType.h" +#include "third_party/skia/include/core/SkColorType.h" #include "third_party/skia/include/gpu/ganesh/GrTypes.h" #include "ui/gfx/color_space.h" #include "ui/gfx/geometry/rect.h" @@ -4649,12 +4650,15 @@ if (!*readback_result) { return GL_FALSE; } - // We need to use `RelaxedAtomicWriteMemcpy` because we might be writing into - // memory observed by JS at the same time. auto dst = UNSAFE_TODO(base::span(static_cast<uint8_t*>(pixels), dst_size)); auto src = UNSAFE_TODO( base::span(static_cast<uint8_t*>(shm_address) + pixels_offset, dst_size)); - base::subtle::RelaxedAtomicWriteMemcpy(dst, src); + size_t min_row_bytes = + dst_width * + SkColorTypeBytesPerPixel(static_cast<SkColorType>(dst_sk_color_type)); + RelaxedAtomicWriteMemcpyImageRowsSkippingPadding( + /*dst=*/dst, /*src=*/src, /*row_bytes=*/min_row_bytes, + /*height=*/dst_height, /*stride=*/dst_row_bytes); return GL_TRUE; } diff --git a/gpu/command_buffer/client/gles2_implementation_unittest.cc b/gpu/command_buffer/client/gles2_implementation_unittest.cc index 51380ad6c..0de18a57 100644 --- a/gpu/command_buffer/client/gles2_implementation_unittest.cc +++ b/gpu/command_buffer/client/gles2_implementation_unittest.cc @@ -17,6 +17,7 @@ #include <array> #include <memory> +#include "base/bits.h" #include "base/compiler_specific.h" #include "base/containers/heap_array.h" #include "base/containers/span.h" @@ -403,7 +404,8 @@ ExpectedMemoryInfo mem; // Temporarily allocate memory and expect that memory block to be reused. - mem.ptr = gl_->mapped_memory_->Alloc(size, &mem.id, &mem.offset).data(); + mem.span = gl_->mapped_memory_->Alloc(size, &mem.id, &mem.offset); + mem.ptr = mem.span.data(); gl_->mapped_memory_->Free(mem.ptr); return mem; @@ -3864,6 +3866,75 @@ "but written again")); } +TEST_F(GLES2ImplementationTest, ReadbackARGBImagePixelsINTERNALPadding) { + gpu::Mailbox mailbox = gpu::Mailbox::Generate(); + + GLuint dst_width = 2; + GLuint dst_height = 2; + GLuint dst_sk_color_type = 4; // kRGBA_8888_SkColorType + GLuint dst_sk_alpha_type = 1; // kPremul_SkAlphaType + GLuint dst_row_bytes = + 12; // 2 pixels * 4 bytes/pixel = 8 bytes. Row padding = 4 bytes. + GLuint dst_size = dst_height * dst_row_bytes; + + GLuint color_space_offset = base::bits::AlignUp( + sizeof(cmds::ReadbackARGBImagePixelsINTERNAL::Result), sizeof(uint64_t));
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/test/data/gpu/webcodecs/copyTo.html b/content/test/data/gpu/webcodecs/copyTo.html
index efef2057..5bca638 100644
--- a/content/test/data/gpu/webcodecs/copyTo.html
+++ b/content/test/data/gpu/webcodecs/copyTo.html
@@ -198,6 +198,78 @@
worker.terminate();
}
+ // Test that padding bytes (stride/offset padding) are not overwritten.
+ {
+ const PAD = 0xAA;
+ const options = {
+ layout: [
+ {offset: 5, stride: frame.displayWidth * 4 + 16}, // plenty of padding
+ ],
+ };
+ if (frame.format === 'I420' || frame.format === 'I420A') {
+ options.layout = [
+ {offset: 5, stride: frame.displayWidth + 16},
+ {offset: (frame.displayWidth + 16) * frame.displayHeight + 10, stride: frame.displayWidth / 2 + 16},
+ {offset: (frame.displayWidth + 16) * frame.displayHeight + (frame.displayWidth / 2 + 16) * (frame.displayHeight / 2) + 20, stride: frame.displayWidth / 2 + 16},
+ ];
+ if (frame.format === 'I420A') {
+ options.layout.push({
+ offset: (frame.displayWidth + 16) * frame.displayHeight + (frame.displayWidth / 2 + 16) * (frame.displayHeight / 2) * 2 + 30,
+ stride: frame.displayWidth + 16
+ });
+ }
+ } else if (frame.format === 'NV12') {
+ options.layout = [
+ {offset: 5, stride: frame.displayWidth + 16},
+ {offset: (frame.displayWidth + 16) * frame.displayHeight + 10, stride: frame.displayWidth + 16},
+ ];
+ }
+
+ let size_with_padding = frame.allocationSize(options);
+ let buf = new ArrayBuffer(size_with_padding);
+ let view = new Uint8Array(buf);
+ view.fill(PAD);
+
+ let layout = await frame.copyTo(buf, options);
+
+ // Verify that the layout matches what we specified
+ for (let plane = 0; plane < layout.length; plane++) {
+ TEST.assert(layout[plane].offset === options.layout[plane].offset, "offset mismatch");
+ TEST.assert(layout[plane].stride === options.layout[plane].stride, "stride mismatch");
+ }
+
+ // We want to verify that the bytes between rows (the padding bytes) are untouched (still PAD).
+ for (let plane = 0; plane < layout.length; plane++) {
+ let plane_offset = layout[plane].offset;
+ let stride = layout[plane].stride;
+ let plane_height = frame.displayHeight;
+ let plane_width = frame.displayWidth;
+ if (plane > 0 && (frame.format === 'I420' || frame.format === 'I420A' || frame.format === 'NV12')) {
+ plane_height /= 2;
+ if (frame.format === 'I420' || frame.format === 'I420A') {
+ plane_width /= 2;
+ }
+ }
+ let bytes_per_pixel = 1;
+ if (frame.format === 'RGBA' || frame.format === 'RGBX' || frame.format === 'BGRA' || frame.format === 'BGRX') {
+ bytes_per_pixel = 4;
+ } else if (frame.format === 'NV12' && plane === 1) {
+ bytes_per_pixel = 2; // UV interleaved
+ }
+
+ let row_bytes = plane_width * bytes_per_pixel;
+ for (let y = 0; y < plane_height; ++y) {
+ let row_start = plane_offset + y * stride;
+ for (let x = row_bytes; x < stride; ++x) {
+ let pad_idx = row_start + x;
+ if (pad_idx < size_with_padding) {
+ TEST.assert(view[pad_idx] === PAD, `Padding byte overwritten at index ${pad_idx} for plane ${plane}, row ${y}, x ${x}. Expected ${PAD}, got ${view[pad_idx]}`);
+ }
+ }
+ }
+ }
+ }
+
// Validate pixels
if (!arg.validate_camera_frames && source_type == 'camera') {
TEST.log("Skip copyTo result validation");
diff --git a/gpu/command_buffer/client/gles2_implementation_unittest.cc b/gpu/command_buffer/client/gles2_implementation_unittest.cc
index 51380ad6c..0de18a57 100644
--- a/gpu/command_buffer/client/gles2_implementation_unittest.cc
+++ b/gpu/command_buffer/client/gles2_implementation_unittest.cc
@@ -17,6 +17,7 @@
#include <array>
#include <memory>
+#include "base/bits.h"
#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
@@ -403,7 +404,8 @@
ExpectedMemoryInfo mem;
// Temporarily allocate memory and expect that memory block to be reused.
- mem.ptr = gl_->mapped_memory_->Alloc(size, &mem.id, &mem.offset).data();
+ mem.span = gl_->mapped_memory_->Alloc(size, &mem.id, &mem.offset);
+ mem.ptr = mem.span.data();
gl_->mapped_memory_->Free(mem.ptr);
return mem;
@@ -3864,6 +3866,75 @@
"but written again"));
}
+TEST_F(GLES2ImplementationTest, ReadbackARGBImagePixelsINTERNALPadding) {
+ gpu::Mailbox mailbox = gpu::Mailbox::Generate();
+
+ GLuint dst_width = 2;
+ GLuint dst_height = 2;
+ GLuint dst_sk_color_type = 4; // kRGBA_8888_SkColorType
+ GLuint dst_sk_alpha_type = 1; // kPremul_SkAlphaType
+ GLuint dst_row_bytes =
+ 12; // 2 pixels * 4 bytes/pixel = 8 bytes. Row padding = 4 bytes.
+ GLuint dst_size = dst_height * dst_row_bytes;
+
+ GLuint color_space_offset = base::bits::AlignUp(
+ sizeof(cmds::ReadbackARGBImagePixelsINTERNAL::Result), sizeof(uint64_t));
+ GLuint mailbox_offset = color_space_offset;
+ GLuint pixels_offset = base::bits::AlignUp(
+ mailbox_offset + sizeof(gpu::Mailbox), sizeof(uint64_t));
+
+ GLuint total_size =
+ pixels_offset +
+ base::bits::AlignUp(dst_size, static_cast<GLuint>(sizeof(uint64_t)));
+
+ ExpectedMemoryInfo mem = GetExpectedMappedMemory(total_size);
+
+ std::vector<uint8_t> dst_pixels(dst_size, 0xAA);
+
+ EXPECT_CALL(*command_buffer(), OnFlush())
+ .WillOnce([mem, pixels_offset, dst_size]() {
+ // Write 1 to readback_result (at the beginning of shm).
+ auto* result =
+ reinterpret_cast<cmds::ReadbackARGBImagePixelsINTERNAL::Result*>(
+ mem.ptr);
+ *result = 1;
+
+ // Write test data to the pixel portion of the shared memory.
+ auto src_pixels = mem.span.subspan(pixels_offset, dst_size);
+ // Fill src_pixels with distinct values, e.g. 1 to dst_size
+ for (size_t i = 0; i < dst_size; ++i) {
+ src_pixels[i] = static_cast<uint8_t>(i + 1);
+ }
+ })
+ .RetiresOnSaturation();
+
+ GLboolean success = gl_->ReadbackARGBImagePixelsINTERNAL(
+ mailbox.name, /*dst_color_space=*/nullptr,
+ /*dst_color_space_size=*/0, dst_size, dst_width, dst_height,
+ dst_sk_color_type, dst_sk_alpha_type, dst_row_bytes, /*src_x=*/0,
+ /*src_y=*/0, /*plane_index=*/0, dst_pixels.data());
+
+ EXPECT_TRUE(success);
+
+ // Expected output:
+ // Row 1 (pixels: 0 to 7) copied from src_pixels (0 to 7): 1, 2, 3, 4, 5, 6,
+ // 7, 8. Row 1 (padding: 8 to 11) untouched: 0xAA, 0xAA, 0xAA, 0xAA. Row 2
+ // (pixels: 12 to 19) copied from src_pixels (12 to 19): 13, 14, 15, 16, 17,
+ // 18, 19, 20. Row 2 (padding: 20 to 23) untouched: 0xAA, 0xAA, 0xAA, 0xAA.
+
+ std::vector<uint8_t> expected_pixels(dst_size, 0xAA);
+ size_t min_row_bytes = 8; // 2 pixels * 4 bytes/pixel = 8.
+ for (size_t y = 0; y < dst_height; ++y) {
+ for (size_t x = 0; x < min_row_bytes; ++x) {
+ size_t dst_idx = y * dst_row_bytes + x;
+ size_t src_idx = y * dst_row_bytes + x;
+ expected_pixels[dst_idx] = static_cast<uint8_t>(src_idx + 1);
+ }
+ }
+
+ EXPECT_EQ(dst_pixels, expected_pixels);
+}
+
#include "gpu/command_buffer/client/gles2_implementation_unittest_autogen.h"
} // namespace gles2
diff --git a/gpu/command_buffer/client/raster_implementation_unittest.cc b/gpu/command_buffer/client/raster_implementation_unittest.cc
index e8b306e..d9fd57a 100644
--- a/gpu/command_buffer/client/raster_implementation_unittest.cc
+++ b/gpu/command_buffer/client/raster_implementation_unittest.cc
@@ -16,6 +16,7 @@
#include <array>
#include <memory>
+#include "base/bits.h"
#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
@@ -254,7 +255,8 @@
ExpectedMemoryInfo mem;
// Temporarily allocate memory and expect that memory block to be reused.
- mem.ptr = gl_->mapped_memory_->Alloc(size, &mem.id, &mem.offset).data();
+ mem.span = gl_->mapped_memory_->Alloc(size, &mem.id, &mem.offset);
+ mem.ptr = mem.span.data();
gl_->mapped_memory_->Free(mem.ptr);
return mem;
@@ -275,6 +277,19 @@
return gl_->GetBucketContents(bucket_id, data);
}
+ bool ReadbackImagePixelsINTERNAL(const gpu::Mailbox& source_mailbox,
+ const SkImageInfo& dst_info,
+ GLuint dst_row_bytes,
+ int src_x,
+ int src_y,
+ int plane_index,
+ base::OnceCallback<void(bool)> readback_done,
+ void* dst_pixels) {
+ return gl_->ReadbackImagePixelsINTERNAL(
+ source_mailbox, dst_info, dst_row_bytes, src_x, src_y, plane_index,
+ std::move(readback_done), dst_pixels);
+ }
+
static SharedMemoryLimits SharedMemoryLimitsForTesting() {
SharedMemoryLimits limits;
limits.command_buffer_size = kCommandBufferSizeBytes;
@@ -879,6 +894,275 @@
EXPECT_TRUE(NoCommandsWritten());
}
+// https://crbug.com/543707066
+TEST_F(RasterImplementationTest, ReadbackImagePixelsSyncPadding) {
+ gpu::Mailbox mailbox = gpu::Mailbox::Generate();
+ SkImageInfo dst_info = SkImageInfo::MakeN32Premul(2, 2);
+ GLuint dst_row_bytes =
+ 12; // 2 pixels * 4 bytes/pixel = 8 bytes. Row padding = 4 bytes.
+
+ GLuint color_space_offset = base::bits::AlignUp(
+ sizeof(cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result),
+ sizeof(uint64_t));
+ GLuint pixels_offset = color_space_offset;
+ GLuint dst_size = dst_info.computeByteSize(dst_row_bytes);
+ GLuint total_size =
+ pixels_offset +
+ base::bits::AlignUp(dst_size, static_cast<GLuint>(sizeof(uint64_t)));
+
+ ExpectedMemoryInfo mem = GetExpectedMappedMemory(total_size);
+
+ std::vector<uint8_t> dst_pixels(dst_row_bytes * dst_info.height(), 0xAA);
+
+ EXPECT_CALL(*command_buffer(), OnFlush())
+ .WillOnce([mem, pixels_offset, dst_size]() {
+ // Write 1 to readback_result (at the beginning of shm).
+ auto* result = reinterpret_cast<
+ cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result*>(mem.ptr);
+ *result = 1;
+
+ // Write test data to the pixel portion of the shared memory.
+ auto src_pixels = mem.span.subspan(pixels_offset, dst_size);
+ // Fill src_pixels with distinct values, e.g. 1 to dst_size
+ for (size_t i = 0; i < dst_size; ++i) {
+ src_pixels[i] = static_cast<uint8_t>(i + 1);
+ }
+ })
+ .RetiresOnSaturation();
+
+ bool success = gl_->ReadbackImagePixels(mailbox, dst_info, dst_row_bytes,
+ /*src_x=*/0, /*src_y=*/0,
+ /*plane_index=*/0, dst_pixels.data());
+
+ EXPECT_TRUE(success);
+
+ // Expected output:
+ // Row 1 (pixels: 0 to 7) copied from src_pixels (0 to 7): 1, 2, 3, 4, 5, 6,
+ // 7, 8. Row 1 (padding: 8 to 11) untouched: 0xAA, 0xAA, 0xAA, 0xAA. Row 2
+ // (pixels: 12 to 19) copied from src_pixels (12 to 19): 13, 14, 15, 16, 17,
+ // 18, 19, 20. Row 2 (padding: 20 to 23) untouched: 0xAA, 0xAA, 0xAA, 0xAA.
+
+ std::vector<uint8_t> expected_pixels(dst_row_bytes * dst_info.height(), 0xAA);
+ for (int y = 0; y < dst_info.height(); ++y) {
+ for (size_t x = 0; x < dst_info.minRowBytes(); ++x) {
+ size_t dst_idx = y * dst_row_bytes + x;
+ size_t src_idx = y * dst_row_bytes + x;
+ expected_pixels[dst_idx] = static_cast<uint8_t>(src_idx + 1);
+ }
+ }
+
+ EXPECT_EQ(dst_pixels, expected_pixels);
+}
+
+// https://crbug.com/543707066
+TEST_F(RasterImplementationTest, ReadbackImagePixelsAsyncPadding) {
+ gpu::Mailbox mailbox = gpu::Mailbox::Generate();
+ SkImageInfo dst_info = SkImageInfo::MakeN32Premul(2, 2);
+ GLuint dst_row_bytes =
+ 12; // 2 pixels * 4 bytes/pixel = 8 bytes. Row padding = 4 bytes.
+
+ GLuint color_space_offset = base::bits::AlignUp(
+ sizeof(cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result),
+ sizeof(uint64_t));
+ GLuint pixels_offset = color_space_offset;
+ GLuint dst_size = dst_info.computeByteSize(dst_row_bytes);
+ GLuint total_size =
... (truncated)
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page