CVE-2026-19173
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forskia/public/mojom/test/mojom_traits_unittest.cc |
modified |
Files Changed
skia/public/mojom/hdr_metadata_mojom_traits.hskia/public/mojom/test/mojom_traits_unittest.cc
Patch
From 36e3c8b0d681b51b59b71238630c97d291639c95 Mon Sep 17 00:00:00 2001
From: Christopher Cameron <ccameron@chromium.org>
Date: Mon, 27 Jul 2026 15:53:34 -0700
Subject: [PATCH] skhdr::AdaptiveGlobalToneMap: Reject invalid metadata
Validate metadata after receiving it. Reject IPCs with invalid metadata.
Update a test that was specifying invalid metadata, add tests of various
types of invalid metadata.
Bug: 538332338
Change-Id: Icdada0edf15d51fce2fe22af88f55e00d1dba65d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8157200
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: ccameron chromium <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1669050}
---
diff --git a/skia/public/mojom/hdr_metadata_mojom_traits.h b/skia/public/mojom/hdr_metadata_mojom_traits.h
index eb4d4af..875ac1e 100644
--- a/skia/public/mojom/hdr_metadata_mojom_traits.h
+++ b/skia/public/mojom/hdr_metadata_mojom_traits.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef SKIA_PUBLIC_MOJOM_HDR_METADATA_MO_TRAITS_H_
-#define SKIA_PUBLIC_MOJOM_HDR_METADATA_MO_TRAITS_H_
+#ifndef SKIA_PUBLIC_MOJOM_HDR_METADATA_MOJOM_TRAITS_H_
+#define SKIA_PUBLIC_MOJOM_HDR_METADATA_MOJOM_TRAITS_H_
#include <cmath>
@@ -265,6 +265,9 @@
if (!data.ReadHeadroomAdaptiveToneMap(&out->fHeadroomAdaptiveToneMap)) {
return false;
}
+ if (!out->isValid()) {
+ return false;
+ }
return true;
}
};
diff --git a/skia/public/mojom/test/mojom_traits_unittest.cc b/skia/public/mojom/test/mojom_traits_unittest.cc
index dc2af641..6fc5019 100644
--- a/skia/public/mojom/test/mojom_traits_unittest.cc
+++ b/skia/public/mojom/test/mojom_traits_unittest.cc
@@ -722,8 +722,8 @@
.fGreen = 0.2f,
.fBlue = 0.3f,
.fMax = 0.4f,
- .fMin = 0.5f,
- .fComponent = 0.6f,
+ .fMin = 0.0f,
+ .fComponent = 0.0f,
},
.fGainCurve =
{
@@ -872,6 +872,30 @@
EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
}
+ {
+ skhdr::AdaptiveGlobalToneMap bad = in;
+ bad.fHeadroomAdaptiveToneMap->fAlternateImages[0]
+ .fColorGainFunction.fGainCurve.fControlPoints.clear();
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
+ skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
+ }
+ {
+ skhdr::AdaptiveGlobalToneMap bad = in;
+ bad.fHeadroomAdaptiveToneMap->fAlternateImages[0]
+ .fColorGainFunction.fGainCurve.fControlPoints.resize(33);
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
+ skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
+ }
+ {
+ skhdr::AdaptiveGlobalToneMap bad = in;
+ for (size_t i = 0; i < 4; ++i) {
+ auto alt = bad.fHeadroomAdaptiveToneMap->fAlternateImages.front();
+ alt.fHdrHeadroom += static_cast<float>(i);
+ bad.fHeadroomAdaptiveToneMap->fAlternateImages.push_back(alt);
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
+ skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
+ }
}
} // namespace
Regression Test / PoC
diff --git a/skia/public/mojom/test/mojom_traits_unittest.cc b/skia/public/mojom/test/mojom_traits_unittest.cc
index dc2af641..6fc5019 100644
--- a/skia/public/mojom/test/mojom_traits_unittest.cc
+++ b/skia/public/mojom/test/mojom_traits_unittest.cc
@@ -722,8 +722,8 @@
.fGreen = 0.2f,
.fBlue = 0.3f,
.fMax = 0.4f,
- .fMin = 0.5f,
- .fComponent = 0.6f,
+ .fMin = 0.0f,
+ .fComponent = 0.0f,
},
.fGainCurve =
{
@@ -872,6 +872,30 @@
EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
}
+ {
+ skhdr::AdaptiveGlobalToneMap bad = in;
+ bad.fHeadroomAdaptiveToneMap->fAlternateImages[0]
+ .fColorGainFunction.fGainCurve.fControlPoints.clear();
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
+ skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
+ }
+ {
+ skhdr::AdaptiveGlobalToneMap bad = in;
+ bad.fHeadroomAdaptiveToneMap->fAlternateImages[0]
+ .fColorGainFunction.fGainCurve.fControlPoints.resize(33);
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
+ skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
+ }
+ {
+ skhdr::AdaptiveGlobalToneMap bad = in;
+ for (size_t i = 0; i < 4; ++i) {
+ auto alt = bad.fHeadroomAdaptiveToneMap->fAlternateImages.front();
+ alt.fHdrHeadroom += static_cast<float>(i);
+ bad.fHeadroomAdaptiveToneMap->fAlternateImages.push_back(alt);
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
+ skia::mojom::SkHdrAdaptiveGlobalToneMap>(bad, out));
+ }
}
} // namespace
Original Bug Report
[GPU/Skia] AGTM structured metadata causes an OOB stack write in the GPU process
Brief description
The structured Mojo path accepts an arbitrary number of AGTM
alternate_images, while Skia allocates arrays for at most four alternate
images plus the baseline. A renderer-authored AGTM object with 64 entries
reaches skhdr::AgtmHelpers::ComputeWeighting() during GPU OOP raster and
writes beyond both fixed-size arrays.
This is a compromised-renderer simulation. The HTML page contains only a normal P3/PQ AVIF image; the attached renderer-only patch supplies the malformed structured metadata at the paint serialization boundary. I do not claim that unmodified web content can create this object, and I do not claim RCE, instruction-pointer control, or sandbox escape.
Reproduced result
On the attached full Chrome ASan build, the GPU process reached:
skhdr::AgtmHelpers::ComputeWeighting
third_party/skia/src/codec/SkHdrAgtm.cpp:471:1
skhdr::AgtmHelpers::MakeColorFilter
third_party/skia/src/codec/SkHdrAgtm.cpp:533:28
skhdr::Metadata::makeToneMapColorFilter
third_party/skia/src/codec/SkHdrMetadata.cpp:255:12
cc::ToneMapUtil::AddGlobalToneMapFilterToPaint
cc/paint/tone_map_util.cc:98:40
gpu::raster::RasterDecoderImpl::DoRasterCHROMIUM
gpu/command_buffer/service/raster_decoder.cc:3067:22
The GPU process reported Received signal 4 ILL_ILLOPN and exited with
exit_code=4. The complete raw and symbolized output is attached as
asan.log. This ASan build emitted Chromium’s signal stack rather than a
conventional ERROR: AddressSanitizer red-zone report; that is stated
explicitly in the attachment.
Version and environment
- Chrome build identifier:
152.0.7951.0 - Chromium revision:
6e23d2ad7440702de4442bc8e8627e95b2aa3285 - Skia revision:
ca065587acac8432379ae196812dd3ec663129c2 - OS: Ubuntu 24.04, Linux x86_64, kernel
7.0.0-28-generic - Build: ASan component build,
is_debug=false,is_asan=true,is_component_build=true,dcheck_always_on=true,symbol_level=1
Reproduction
- Check out Chromium revision
6e23d2ad7440702de4442bc8e8627e95b2aa3285. - Apply
renderer_simulation.patchand buildout/asan/chrome. - Place
poc.htmlbesidecosmos1650_yuv444_10bpc_p3pq.avif. - Run the full Chrome binary with a clean temporary profile:
ASAN_SYMBOLIZER_PATH=third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer \
ASAN_OPTIONS=detect_odr_violation=0:abort_on_error=1:symbolize=1 \
out/asan/chrome --user-data-dir=/tmp/agtm-poc-profile \
--enable-gpu-rasterization file:///ABSOLUTE/PATH/poc.html
- Wait for the first image raster task. The GPU process should terminate as
shown above. Save stdout/stderr as
asan.log.
Before the attached capture, the host was configured for the developer build’s user namespace and verified with:
kernel.apparmor_restrict_unprivileged_userns = 0
unshare -Ur true -> exit 0
The attached asan.log was then produced with no --no-sandbox and no
--disable-gpu-sandbox. The outer timeout returned 124 only because the
browser remained alive after the GPU process exited; the GPU process itself
reported exit_code=4.
No custom harness, CDP, browser test, or unit-test executable is required.
Root cause
skia/public/mojom/hdr_metadata.mojom:65 declares an unbounded
alternate_images array. The traits read it without enforcing Skia’s
kMaxNumAlternateImages = 4 limit. In
third_party/skia/src/codec/SkHdrAgtm.cpp, lines 412 and 416 allocate
H[5] and indices[5], while lines 417-429 iterate over the entire vector
and write H[N++]/indices[N].
The renderer simulation is guarded by --type=renderer; it changes neither
the GPU consumer nor the Skia code. The attached patch is not a proposed fix.
Attachments
Upload these as individual files:
poc.htmlcosmos1650_yuv444_10bpc_p3pq.avifrenderer_simulation.patchrepro.mdasan.log
The complete symbolized trace and all additional capture metadata are in
asan.log, not only in this report.
Suggested remediation
Enforce the maximum in the structured Mojo boundary and validate AGTM before Skia consumes it. Add regression coverage for 5 and 64 alternate images in the Mojo traits and GPU OOP-raster path.