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 = &subset;
+    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;
 }