aboutsummaryrefslogtreecommitdiffhomepage
path: root/fuzz/fuzz.cpp
diff options
context:
space:
mode:
authorGravatar kjlubick <kjlubick@google.com>2016-02-16 16:14:23 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2016-02-16 16:14:23 -0800
commit2a42f48b58f11c32017e2da6c8468a3e2cd9bd8c (patch)
tree1708a524805d326c36a3e18140ee59513499f7e6 /fuzz/fuzz.cpp
parentff9febf82f5c9329c2899287c771348dba9e83ae (diff)
Add ability to fuzz images using scaling and different modes
This also fixes the tryAllocPixels/SkColorTable mismatch which was causing the "Image might be too large (32 x 32)" problems. BUG=skia:4952 GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1698963003 Review URL: https://codereview.chromium.org/1698963003
Diffstat (limited to 'fuzz/fuzz.cpp')
-rw-r--r--fuzz/fuzz.cpp251
1 files changed, 233 insertions, 18 deletions
diff --git a/fuzz/fuzz.cpp b/fuzz/fuzz.cpp
index 02bf76e665..596000d408 100644
--- a/fuzz/fuzz.cpp
+++ b/fuzz/fuzz.cpp
@@ -20,21 +20,23 @@
#include <signal.h>
#include <stdlib.h>
+// TODO(kjlubick): Remove once http://crrev.com/1671193002 lands
__SK_FORCE_IMAGE_DECODER_LINKING;
DEFINE_string2(bytes, b, "", "A path to a file. This can be the fuzz bytes or a binary to parse.");
DEFINE_string2(name, n, "", "If --type is 'api', fuzz the API with this name.");
-DEFINE_string2(type, t, "api", "How to interpret --bytes, either 'image', 'skp', or 'api'.");
-DEFINE_string2(dump, d, "", "If not empty, dump 'image' or 'skp' types as a PNG with this name.");
+DEFINE_string2(type, t, "api", "How to interpret --bytes, either 'image_scale', 'image_mode', 'skp', or 'api'.");
+DEFINE_string2(dump, d, "", "If not empty, dump 'image*' or 'skp' types as a PNG with this name.");
static int printUsage(const char* name) {
SkDebugf("Usage: %s -t <type> -b <path/to/file> [-n api-to-fuzz]\n", name);
return 1;
}
+static uint8_t calculate_option(SkData*);
static int fuzz_api(SkData*);
-static int fuzz_img(SkData*);
+static int fuzz_img(SkData*, uint8_t, uint8_t);
static int fuzz_skp(SkData*);
int main(int argc, char** argv) {
@@ -47,16 +49,39 @@ int main(int argc, char** argv) {
return 2;
}
+ uint8_t option = calculate_option(bytes);
+
if (!FLAGS_type.isEmpty()) {
switch (FLAGS_type[0][0]) {
case 'a': return fuzz_api(bytes);
- case 'i': return fuzz_img(bytes);
+
+ case 'i':
+ // We only allow one degree of freedom to avoid a search space explosion for afl-fuzz.
+ if (FLAGS_type[0][6] == 's') { // image_scale
+ return fuzz_img(bytes, option, 0);
+ }
+ // image_mode
+ return fuzz_img(bytes, 0, option);
case 's': return fuzz_skp(bytes);
}
}
return printUsage(argv[0]);
}
+// This adds up the first 1024 bytes and returns it as an 8 bit integer. This allows afl-fuzz to
+// deterministically excercise different paths, or *options* (such as different scaling sizes or
+// different image modes) without needing to introduce a parameter. This way we don't need a
+// image_scale1, image_scale2, image_scale4, etc fuzzer, we can just have a image_scale fuzzer.
+// Clients are expected to transform this number into a different range, e.g. with modulo (%).
+static uint8_t calculate_option(SkData* bytes) {
+ uint8_t total = 0;
+ const uint8_t* data = bytes->bytes();
+ for (size_t i = 0; i < 1024 && i < bytes->size(); i++) {
+ total += data[i];
+ }
+ return total;
+}
+
int fuzz_api(SkData* bytes) {
const char* name = FLAGS_name.isEmpty() ? "" : FLAGS_name[0];
@@ -86,7 +111,17 @@ static void dump_png(SkBitmap bitmap) {
}
}
-int fuzz_img(SkData* bytes) {
+int fuzz_img(SkData* bytes, uint8_t scale, uint8_t mode) {
+ // We can scale 1x, 2x, 4x, 8x, 16x
+ scale = scale % 5;
+ float fscale = pow(2.0f, scale);
+ SkDebugf("Scaling factor: %d\n", fscale);
+
+ // We have 4 different modes of decoding, just like DM.
+ mode = mode % 4;
+ SkDebugf("Mode: %d\n", mode);
+
+ // This is mostly copied from DMSrcSink's CodecSrc::draw method.
SkDebugf("Decoding\n");
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(bytes));
if (nullptr == codec.get()) {
@@ -95,6 +130,10 @@ int fuzz_img(SkData* bytes) {
}
SkImageInfo decodeInfo = codec->getInfo();
+
+ SkISize size = codec->getScaledDimensions(fscale);
+ decodeInfo = decodeInfo.makeWH(size.width(), size.height());
+
// Construct a color table for the decode if necessary
SkAutoTUnref<SkColorTable> colorTable(nullptr);
SkPMColor* colorPtr = nullptr;
@@ -112,27 +151,203 @@ int fuzz_img(SkData* bytes) {
SkCodec::Options options;
options.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
- if (!bitmap.tryAllocPixels(decodeInfo, &zeroFactory, nullptr)) {
+ if (!bitmap.tryAllocPixels(decodeInfo, &zeroFactory, colorTable.get())) {
SkDebugf("[terminated] Could not allocate memory. Image might be too large (%d x %d)",
- decodeInfo.width(), decodeInfo.height());
+ decodeInfo.width(), decodeInfo.height());
return 4;
}
- switch (codec->getPixels(decodeInfo, bitmap.getPixels(), bitmap.rowBytes(), &options,
- colorPtr, colorCountPtr)) {
- case SkCodec::kSuccess:
+ switch (mode) {
+ case 0: {//kCodecZeroInit_Mode, kCodec_Mode
+ switch (codec->getPixels(decodeInfo, bitmap.getPixels(), bitmap.rowBytes(), &options,
+ colorPtr, colorCountPtr)) {
+ case SkCodec::kSuccess:
+ SkDebugf("[terminated] Success!\n");
+ break;
+ case SkCodec::kIncompleteInput:
+ SkDebugf("[terminated] Partial Success\n");
+ break;
+ case SkCodec::kInvalidConversion:
+ SkDebugf("Incompatible colortype conversion\n");
+ // Crash to allow afl-fuzz to know this was a bug.
+ raise(SIGSEGV);
+ default:
+ SkDebugf("[terminated] Couldn't getPixels.\n");
+ return 6;
+ }
+ break;
+ }
+ case 1: {//kScanline_Mode
+ if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
+ colorCountPtr)) {
+ SkDebugf("[terminated] Could not start scanline decoder\n");
+ return 7;
+ }
+
+ void* dst = bitmap.getAddr(0, 0);
+ size_t rowBytes = bitmap.rowBytes();
+ uint32_t height = decodeInfo.height();
+ switch (codec->getScanlineOrder()) {
+ case SkCodec::kTopDown_SkScanlineOrder:
+ case SkCodec::kBottomUp_SkScanlineOrder:
+ case SkCodec::kNone_SkScanlineOrder:
+ // We do not need to check the return value. On an incomplete
+ // image, memory will be filled with a default value.
+ codec->getScanlines(dst, height, rowBytes);
+ break;
+ case SkCodec::kOutOfOrder_SkScanlineOrder: {
+ for (int y = 0; y < decodeInfo.height(); y++) {
+ int dstY = codec->outputScanline(y);
+ void* dstPtr = bitmap.getAddr(0, dstY);
+ // We complete the loop, even if this call begins to fail
+ // due to an incomplete image. This ensures any uninitialized
+ // memory will be filled with the proper value.
+ codec->getScanlines(dstPtr, 1, bitmap.rowBytes());
+ }
+ break;
+ }
+ }
SkDebugf("[terminated] Success!\n");
break;
- case SkCodec::kIncompleteInput:
- SkDebugf("[terminated] Partial Success\n");
+ }
+ case 2: { //kStripe_Mode
+ const int height = decodeInfo.height();
+ // This value is chosen arbitrarily. We exercise more cases by choosing a value that
+ // does not align with image blocks.
+ const int stripeHeight = 37;
+ const int numStripes = (height + stripeHeight - 1) / stripeHeight;
+
+ // Decode odd stripes
+ if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
+ colorCountPtr)
+ || SkCodec::kTopDown_SkScanlineOrder != codec->getScanlineOrder()) {
+ // This mode was designed to test the new skip scanlines API in libjpeg-turbo.
+ // Jpegs have kTopDown_SkScanlineOrder, and at this time, it is not interesting
+ // to run this test for image types that do not have this scanline ordering.
+ SkDebugf("[terminated] Could not start top-down scanline decoder\n");
+ return 8;
+ }
+
+ for (int i = 0; i < numStripes; i += 2) {
+ // Skip a stripe
+ const int linesToSkip = SkTMin(stripeHeight, height - i * stripeHeight);
+ codec->skipScanlines(linesToSkip);
+
+ // Read a stripe
+ const int startY = (i + 1) * stripeHeight;
+ const int linesToRead = SkTMin(stripeHeight, height - startY);
+ if (linesToRead > 0) {
+ codec->getScanlines(bitmap.getAddr(0, startY), linesToRead, bitmap.rowBytes());
+ }
+ }
+
+ // Decode even stripes
+ const SkCodec::Result startResult = codec->startScanlineDecode(decodeInfo, nullptr,
+ colorPtr, colorCountPtr);
+ if (SkCodec::kSuccess != startResult) {
+ SkDebugf("[terminated] Failed to restart scanline decoder with same parameters.\n");
+ return 9;
+ }
+ for (int i = 0; i < numStripes; i += 2) {
+ // Read a stripe
+ const int startY = i * stripeHeight;
+ const int linesToRead = SkTMin(stripeHeight, height - startY);
+ codec->getScanlines(bitmap.getAddr(0, startY), linesToRead, bitmap.rowBytes());
+
+ // Skip a stripe
+ const int linesToSkip = SkTMin(stripeHeight, height - (i + 1) * stripeHeight);
+ if (linesToSkip > 0) {
+ codec->skipScanlines(linesToSkip);
+ }
+ }
+ SkDebugf("[terminated] Success!\n");
break;
- case SkCodec::kInvalidConversion:
- SkDebugf("[terminated] Incompatible colortype conversion\n");
- return 5;
+ }
+ case 3: { //kSubset_Mode
+ // Arbitrarily choose a divisor.
+ int divisor = 2;
+ // Total width/height of the image.
+ const int W = codec->getInfo().width();
+ const int H = codec->getInfo().height();
+ if (divisor > W || divisor > H) {
+ SkDebugf("[terminated] Cannot codec subset: divisor %d is too big "
+ "with dimensions (%d x %d)\n", divisor, W, H);
+ return 10;
+ }
+ // subset dimensions
+ // SkWebpCodec, the only one that supports subsets, requires even top/left boundaries.
+ const int w = SkAlign2(W / divisor);
+ const int h = SkAlign2(H / divisor);
+ SkIRect subset;
+ SkCodec::Options opts;
+ opts.fSubset = &subset;
+ SkBitmap subsetBm;
+ // We will reuse pixel memory from bitmap.
+ void* pixels = bitmap.getPixels();
+ // Keep track of left and top (for drawing subsetBm into canvas). We could use
+ // fscale * x and fscale * y, but we want integers such that the next subset will start
+ // where the last one ended. So we'll add decodeInfo.width() and height().
+ int left = 0;
+ for (int x = 0; x < W; x += w) {
+ int top = 0;
+ for (int y = 0; y < H; y+= h) {
+ // Do not make the subset go off the edge of the image.
+ const int preScaleW = SkTMin(w, W - x);
+ const int preScaleH = SkTMin(h, H - y);
+ subset.setXYWH(x, y, preScaleW, preScaleH);
+ // And fscale
+ // FIXME: Should we have a version of getScaledDimensions that takes a subset
+ // into account?
+ decodeInfo = decodeInfo.makeWH(
+ SkTMax(1, SkScalarRoundToInt(preScaleW * fscale)),
+ SkTMax(1, SkScalarRoundToInt(preScaleH * fscale)));
+ size_t rowBytes = decodeInfo.minRowBytes();
+ if (!subsetBm.installPixels(decodeInfo, pixels, rowBytes, colorTable.get(),
+ nullptr, nullptr)) {
+ SkDebugf("[terminated] Could not install pixels.\n");
+ return 11;
+ }
+ const SkCodec::Result result = codec->getPixels(decodeInfo, pixels, rowBytes,
+ &opts, colorPtr, colorCountPtr);
+ switch (result) {
+ case SkCodec::kSuccess:
+ case SkCodec::kIncompleteInput:
+ SkDebugf("okay\n");
+ break;
+ case SkCodec::kInvalidConversion:
+ if (0 == (x|y)) {
+ // First subset is okay to return unimplemented.
+ SkDebugf("[terminated] Incompatible colortype conversion\n");
+ return 12;
+ }
+ // If the first subset succeeded, a later one should not fail.
+ // fall through to failure
+ case SkCodec::kUnimplemented:
+ if (0 == (x|y)) {
+ // First subset is okay to return unimplemented.
+ SkDebugf("[terminated] subset codec not supported\n");
+ return 13;
+ }
+ // If the first subset succeeded, why would a later one fail?
+ // fall through to failure
+ default:
+ SkDebugf("[terminated] subset codec failed to decode (%d, %d, %d, %d) "
+ "with dimensions (%d x %d)\t error %d\n",
+ x, y, decodeInfo.width(), decodeInfo.height(),
+ W, H, result);
+ return 14;
+ }
+ // translate by the scaled height.
+ top += decodeInfo.height();
+ }
+ // translate by the scaled width.
+ left += decodeInfo.width();
+ }
+ SkDebugf("[terminated] Success!\n");
+ break;
+ }
default:
- // Everything else is considered a failure.
- SkDebugf("[terminated] Couldn't getPixels.\n");
- return 6;
+ SkDebugf("[terminated] Mode not implemented yet\n");
}
dump_png(bitmap);