Exercise subset decoding in FuzzAndroidCodec Extend FuzzAndroidCodec to test getSupportedSubset(), getSampledSubsetDimensions(), and getAndroidPixels() with fSubset alongside fSampleSize so that subset-specific bugs (such as the OOB write fixed in b/562438701) are covered by fuzzing. Bug: b/562799124 Change-Id: I13837da3c2f27c5e3e68ff1aa0ce055fdf99e8ec Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1379817 Commit-Queue: Elliot Sisteron <elliotsisteron@google.com> Reviewed-by: Florin Malita <fmalita@google.com> Reviewed-by: Ćukasz Anforowicz <lukasza@google.com>
diff --git a/fuzz/oss_fuzz/FuzzAndroidCodec.cpp b/fuzz/oss_fuzz/FuzzAndroidCodec.cpp index b22b3d5..d20b79b 100644 --- a/fuzz/oss_fuzz/FuzzAndroidCodec.cpp +++ b/fuzz/oss_fuzz/FuzzAndroidCodec.cpp
@@ -6,14 +6,80 @@ */ #include "include/codec/SkAndroidCodec.h" +#include "include/codec/SkCodec.h" +#include "include/codec/SkEncodedImageFormat.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkRect.h" +#include "include/core/SkSize.h" #include "include/core/SkStream.h" #include "include/core/SkSurface.h" #include "include/private/SkGainmapInfo.h" #include "fuzz/Fuzz.h" +static bool supports_subset_decoding(SkEncodedImageFormat format) { + // TODO(b/568295764): Match `BitmapRegionDecoder::Make`: `SkSampledCodec::onGetSupportedSubset` + // unconditionally returns true, even for bottom-up BMP and ICO codecs whose + // `startScanlineDecode` does not reject `fSubset` and triggers `SkASSERT`s in `SkSampledCodec` + // when `fSubset` is set. + switch (format) { + case SkEncodedImageFormat::kJPEG: + case SkEncodedImageFormat::kPNG: + case SkEncodedImageFormat::kWEBP: + case SkEncodedImageFormat::kHEIF: + case SkEncodedImageFormat::kAVIF: + return true; + default: + return false; + } +} + +// Derives a subset from the trailing bytes of `fuzzData` (rather than a prefix, so plain image +// files remain valid seeds and existing prefix-byte consumption is unchanged) and decodes it. +static void fuzz_android_codec_subset(SkAndroidCodec* codec, + SkSurface* surface, + const uint8_t* fuzzData, + size_t fuzzSize, + uint8_t sampleSize) { + const SkISize origDims = codec->getInfo().dimensions(); + if (fuzzSize < 4 || origDims.isEmpty() || + !supports_subset_decoding(codec->getEncodedFormat())) { + return; + } + + const int x = fuzzData[fuzzSize - 4] % origDims.width(); + const int y = fuzzData[fuzzSize - 3] % origDims.height(); + const int w = (fuzzData[fuzzSize - 2] % (origDims.width() - x)) + 1; + const int h = (fuzzData[fuzzSize - 1] % (origDims.height() - y)) + 1; + SkIRect subset = SkIRect::MakeXYWH(x, y, w, h); + if (!codec->getSupportedSubset(&subset)) { + return; + } + + const SkISize subsetSize = codec->getSampledSubsetDimensions(sampleSize, subset); + SkBitmap subsetBm; + if (!subsetBm.tryAllocPixels(SkImageInfo::MakeN32Premul(subsetSize))) { + return; + } + + SkAndroidCodec::AndroidOptions subsetOptions; + subsetOptions.fSampleSize = sampleSize; + subsetOptions.fSubset = ⊂ + const auto subsetResult = codec->getAndroidPixels( + subsetBm.info(), subsetBm.getPixels(), subsetBm.rowBytes(), &subsetOptions); + switch (subsetResult) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + surface->getCanvas()->drawImage(subsetBm.asImage(), 0, 0); + break; + default: + break; + } +} + bool FuzzAndroidCodec(const uint8_t *fuzzData, size_t fuzzSize, uint8_t sampleSize) { auto codec = SkAndroidCodec::MakeFromStream(SkMemoryStream::MakeDirect(fuzzData, fuzzSize)); if (!codec) { @@ -61,6 +127,7 @@ } surface->getCanvas()->drawImage(bm.asImage(), 0, 0); + fuzz_android_codec_subset(codec.get(), surface.get(), fuzzData, fuzzSize, sampleSize); return true; }