[fuzz] Standardize, document, and backport fuzzing defines.

We had several defines around the code base that were not
very descriptive. Additionally, we had a patch of extra
runtime restrictions living in oss-fuzz that were applied
when fuzzing over there for some fuzzers.

This has all be consolidated and controlled via the defines
documented in site/dev/testing/fuzz.md

As such, we can remove one of the patches that is in oss-fuzz,
taking us closer to being able to fuzz in the CI/CQ.

PS 1 renames existing fuzz defines to the new schema.
PS 2-3 backports skia.diff from oss-fuzz and changes those
definitions to have the _GREATLY modifier.
PS 5+ further condenses the defines so that there is one
define for gating the runtime checks.

Change-Id: Ia4ad96f30c1e9620a2123b510e97c6f501a2e257
Docs-Preview: https://skia.org/?cl=316443
Bug: skia:10713
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/316443
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
Kevin Lubick 2020-09-14 08:37:35 -04:00 committed by Skia Commit-Bot
parent 081bc32703
commit 493f89e577
40 changed files with 137 additions and 37 deletions

View File

@ -22,6 +22,9 @@ DEF_FUZZ(PathMeasure, fuzz) {
FuzzEvilPath(fuzz, &path, SkPath::Verb::kDone_Verb);
SkRect bounds = path.getBounds();
SkScalar maxDim = std::max(bounds.width(), bounds.height());
if (maxDim > 1000000) {
return;
}
SkScalar resScale = maxDim / 1000;
SkPathMeasure measure(path, bits & 1, resScale);
SkPoint position;

13
fuzz/README.md Normal file
View File

@ -0,0 +1,13 @@
We fuzz Skia using oss-fuzz, which in turn uses fuzzing engines such as libfuzzer, afl-fuzz,
hong-fuzz and others.
We define a `fuzzer` to be a targeted bit of code that takes a randomized input and executes code
in a specific area. For example, we have a codec fuzzer which takes a mutated png/jpeg or similar
file and attempts to turn it into an `SkImage`. We also have a canvas fuzzer which takes in a random
set of bytes and turns them into calls on `SkCanvas`.
See [../site/dev/testing/fuzz.md] for more information on building and running fuzzers.
See also:
- [Creating a binary fuzzer](https://docs.google.com/document/d/1QDX0o8yDdmhbjoudNsXc66iuRXRF5XNNqGnzDzX7c2I/edit)
- [Creating an API fuzzer](https://docs.google.com/document/d/1e3ikXO7SwoBsbsi1MF06vydXRlXvYalVORaiUuOXk2Y/edit)

View File

@ -50,7 +50,7 @@ bool FuzzAndroidCodec(sk_sp<SkData> bytes, uint8_t sampleSize) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 10240) {
return 0;

View File

@ -36,7 +36,7 @@ bool FuzzAnimatedImage(sk_sp<SkData> bytes) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 10240) {
return 0;

View File

@ -28,7 +28,7 @@ bool FuzzImageDecode(sk_sp<SkData> bytes) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 10240) {
return 0;

View File

@ -39,7 +39,7 @@ void FuzzImageFilterDeserialize(sk_sp<SkData> bytes) {
}
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 10024) {
return 0;

View File

@ -45,7 +45,7 @@ bool FuzzIncrementalImageDecode(sk_sp<SkData> bytes) {
}
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 10240) {
return 0;

View File

@ -15,7 +15,7 @@ void FuzzJSON(sk_sp<SkData> bytes) {
dom.write(&wstream);
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
auto bytes = SkData::MakeWithoutCopy(data, size);
FuzzJSON(bytes);

View File

@ -26,7 +26,7 @@ void FuzzPathDeserialize(SkReadBuffer& buf) {
s->getCanvas()->drawPath(path, SkPaint());
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 4 || size > 2000) {
return 0;

View File

@ -34,7 +34,7 @@ bool FuzzRegionDeserialize(sk_sp<SkData> bytes) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 512) {
return 0;

View File

@ -36,7 +36,7 @@ void FuzzRegionSetPath(Fuzz* fuzz) {
}
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 512) {
return 0;

View File

@ -27,7 +27,7 @@ void FuzzSKP(sk_sp<SkData> bytes) {
return;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
auto bytes = SkData::MakeWithoutCopy(data, size);
FuzzSKP(bytes);

View File

@ -27,7 +27,7 @@ bool FuzzSKSL2GLSL(sk_sp<SkData> bytes) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 3000) {
return 0;

View File

@ -27,7 +27,7 @@ bool FuzzSKSL2Metal(sk_sp<SkData> bytes) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 3000) {
return 0;

View File

@ -27,7 +27,7 @@ bool FuzzSKSL2Pipeline(sk_sp<SkData> bytes) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 3000) {
return 0;

View File

@ -27,7 +27,7 @@ bool FuzzSKSL2SPIRV(sk_sp<SkData> bytes) {
return true;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 3000) {
return 0;

View File

@ -31,7 +31,7 @@ void FuzzSVG(sk_sp<SkData> bytes) {
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 30000) {
return 0;

View File

@ -27,7 +27,7 @@ void FuzzSkDescriptorDeserialize(sk_sp<SkData> bytes) {
desc->findEntry(tagToFind, &ignore);
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 1024) {
return 0;

View File

@ -74,7 +74,7 @@ bool FuzzSkRuntimeEffect(sk_sp<SkData> bytes) {
return result;
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 3000) {
return 0;

View File

@ -27,7 +27,7 @@ void FuzzTextBlobDeserialize(SkReadBuffer& buf) {
s->getCanvas()->drawTextBlob(tb, 200, 200, SkPaint());
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size > 1024) {
return 0;

View File

@ -420,6 +420,14 @@
#define SK_API_AVAILABLE(...)
#endif
#if defined(SK_BUILD_FOR_LIBFUZZER) || defined(SK_BUILD_FOR_AFL_FUZZ)
#define SK_BUILD_FOR_FUZZER
#endif
#if defined(SK_BUILD_FOR_LIBFUZZER)
SK_API void SkDebugf(const char format[], ...) {}
#endif
/** Called internally if we hit an unrecoverable error.
The platform implementation must not return, but should either throw
an exception or otherwise exit.

View File

@ -64,9 +64,8 @@ static inline void* sk_calloc_throw(size_t size) {
}
static inline void* sk_calloc_canfail(size_t size) {
#if defined(IS_FUZZING_WITH_LIBFUZZER)
// The Libfuzzer environment is very susceptible to OOM, so to avoid those
// just pretend we can't allocate more than 200kb.
#if defined(SK_BUILD_FOR_FUZZER)
// To reduce the chance of OOM, pretend we can't allocate more than 200kb.
if (size > 200000) {
return nullptr;
}
@ -83,9 +82,8 @@ SK_API extern void* sk_realloc_throw(void* buffer, size_t count, size_t elemSize
* These variants return nullptr on failure
*/
static inline void* sk_malloc_canfail(size_t size) {
#if defined(IS_FUZZING_WITH_LIBFUZZER)
// The Libfuzzer environment is very susceptible to OOM, so to avoid those
// just pretend we can't allocate more than 200kb.
#if defined(SK_BUILD_FOR_FUZZER)
// To reduce the chance of OOM, pretend we can't allocate more than 200kb.
if (size > 200000) {
return nullptr;
}

View File

@ -20,7 +20,7 @@ void FuzzSkottieJSON(sk_sp<SkData> bytes) {
animation->seek(0.1337f); // A "nothing up my sleeve" number
}
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
auto bytes = SkData::MakeWithoutCopy(data, size);

View File

@ -19,8 +19,8 @@ less machine- and platform- dependent:
skia_enable_fontmgr_custom_embedded=false
skia_enable_fontmgr_custom_empty=true
All that is needed to reproduce a fuzz downloaded from ClusterFuzz, oss-fuzz or
fuzzer.skia.org is to run something like:
All that is needed to reproduce a fuzz downloaded from ClusterFuzz or oss-fuzz is to
run something like:
out/ASAN/fuzz -b /path/to/downloaded/testcase
@ -69,3 +69,19 @@ Build Skia and your fuzzer entry point:
Run your new fuzzer binary
./a.out
Fuzzing Defines
---------------
There are some defines that can help guide a fuzzer to be more productive (e.g. avoid OOMs, avoid
unnecessarily slow code).
// Required for fuzzing with afl-fuzz to prevent OOMs from adding noise.
SK_BUILD_FOR_AFL_FUZZ
// Required for fuzzing with libfuzzer
SK_BUILD_FOR_LIBFUZZER
// This define adds in guards to abort when we think some code path will take a long time or
// use a lot of RAM. It is set by default when either of the above defines are set.
SK_BUILD_FOR_FUZZER

View File

@ -435,7 +435,7 @@ public:
*/
static SkDngImage* NewFromStream(SkRawStream* stream) {
std::unique_ptr<SkDngImage> dngImage(new SkDngImage(stream));
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_LIBFUZZER)
// Libfuzzer easily runs out of memory after here. To avoid that
// We just pretend all streams are invalid. Our AFL-fuzzer
// should still exercise this code; it's more resistant to OOM.

View File

@ -1546,6 +1546,12 @@ static void operateY(SkAAClip::Builder& builder, const SkAAClip& A,
int topB = iterB.top();
int botB = iterB.bottom();
#if defined(SK_BUILD_FOR_FUZZER)
if ((botA - topA) > 100000 || (botB - topB) > 100000) {
return;
}
#endif
do {
const uint8_t* rowA = nullptr;
const uint8_t* rowB = nullptr;

View File

@ -956,6 +956,12 @@ void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& origPaint,
// transform the path into device space
pathPtr->transform(matrixProvider->localToDevice(), devPathPtr);
#if defined(SK_BUILD_FOR_FUZZER)
if (devPathPtr->countPoints() > 1000) {
return;
}
#endif
this->drawDevPath(*devPathPtr, *paint, drawCoverage, customBlitter, doFill);
}

View File

@ -174,6 +174,12 @@ bool SkImageFilter_Base::Common::unflatten(SkReadBuffer& buffer, int expectedCou
return false;
}
#if defined(SK_BUILD_FOR_FUZZER)
if (count > 4) {
return false;
}
#endif
SkASSERT(fInputs.empty());
for (int i = 0; i < count; i++) {
fInputs.push_back(buffer.readBool() ? buffer.readImageFilter() : nullptr);

View File

@ -34,6 +34,11 @@ sk_sp<SkPixelRef> SkMallocPixelRef::MakeAllocate(const SkImageInfo& info, size_t
if (SkImageInfo::ByteSizeOverflowed(size)) {
return nullptr;
}
#if defined(SK_BUILD_FOR_FUZZER)
if (size > 100000) {
return nullptr;
}
#endif
void* addr = sk_calloc_canfail(size);
if (nullptr == addr) {
return nullptr;

View File

@ -260,6 +260,11 @@ bool SkMaskFilterBase::filterPath(const SkPath& devPath, const SkMatrix& matrix,
SkMask srcM, dstM;
#if defined(SK_BUILD_FOR_FUZZER)
if (devPath.countVerbs() > 1000 || devPath.countPoints() > 1000) {
return false;
}
#endif
if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), this, &matrix, &srcM,
SkMask::kComputeBoundsAndRenderImage_CreateMode,
style)) {

View File

@ -333,6 +333,13 @@ bool SkPaint::getFillPath(const SkPath& src, SkPath* dst, const SkRect* cullRect
SkStrokeRec rec(*this, resScale);
#if defined(SK_BUILD_FOR_FUZZER)
// Prevent lines with small widths from timing out.
if (rec.getStyle() == SkStrokeRec::Style::kStroke_Style && rec.getWidth() < 0.001) {
return false;
}
#endif
const SkPath* srcPtr = &src;
SkPath tmpPath;

View File

@ -3162,7 +3162,11 @@ void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar st
SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
SkASSERT(!oval.isEmpty());
SkASSERT(sweepAngle);
#if defined(SK_BUILD_FOR_FUZZER)
if (sweepAngle > 3600.0f || sweepAngle < -3600.0f) {
return;
}
#endif
path->reset();
path->setIsVolatile(true);
path->setFillType(SkPathFillType::kWinding);

View File

@ -122,7 +122,11 @@ void SkScan::HairLineRgn(const SkPoint array[], int arrayCount, const SkRegion*
if (ix0 == ix1) {// too short to draw
continue;
}
#if defined(SK_BUILD_FOR_FUZZER)
if ((ix1 - ix0) > 100000 || (ix1 - ix0) < 0) {
continue; // too big to draw
}
#endif
SkFixed slope = SkFixedDiv(dy, dx);
SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6);
@ -138,7 +142,11 @@ void SkScan::HairLineRgn(const SkPoint array[], int arrayCount, const SkRegion*
if (iy0 == iy1) { // too short to draw
continue;
}
#if defined(SK_BUILD_FOR_FUZZER)
if ((iy1 - iy0) > 100000 || (iy1 - iy0) < 0) {
continue; // too big to draw
}
#endif
SkFixed slope = SkFixedDiv(dx, dy);
SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6);

View File

@ -175,7 +175,7 @@ void SkPath1DPathEffect::flatten(SkWriteBuffer& buffer) const {
SkScalar SkPath1DPathEffect::next(SkPath* dst, SkScalar distance,
SkPathMeasure& meas) const {
#if defined(IS_FUZZING_WITH_LIBFUZZER)
#if defined(SK_BUILD_FOR_FUZZER)
if (dst->countPoints() > 100000) {
return fAdvance;
}

View File

@ -51,6 +51,11 @@ void Sk2DPathEffect::nextSpan(int x, int y, int count, SkPath* path) const {
if (!fMatrixIsInvertible) {
return;
}
#if defined(SK_BUILD_FOR_FUZZER)
if (count > 100) {
return;
}
#endif
const SkMatrix& mat = this->getMatrix();
SkPoint src, dst;

View File

@ -97,6 +97,11 @@ bool SkDiscretePathEffect::onFilterPath(SkPath* dst, const SkPath& src,
do {
SkScalar length = meas.getLength();
#if defined(SK_BUILD_FOR_FUZZER)
if (length > 1000) {
return false;
}
#endif
if (fSegLength * (2 + doFill) > length) {
meas.getSegment(0, length, dst, true); // to short for us to mangle

View File

@ -213,6 +213,11 @@ void SkLayerDrawLooper::flatten(SkWriteBuffer& buffer) const {
sk_sp<SkFlattenable> SkLayerDrawLooper::CreateProc(SkReadBuffer& buffer) {
int count = buffer.readInt();
#if defined(SK_BUILD_FOR_FUZZER)
if (count > 100) {
count = 100;
}
#endif
Builder builder;
for (int i = 0; i < count; i++) {
LayerInfo info;

View File

@ -811,11 +811,11 @@ bool SkOpCoincidence::addMissing(bool* added DEBUG_COIN_DECLARE_PARAMS()) {
SkOpSegment* outerCoinWritable = const_cast<SkOpSegment*>(outerCoin);
SkOpSegment* outerOppWritable = const_cast<SkOpSegment*>(outerOpp);
SkCoincidentSpans* inner = outer;
#ifdef IS_FUZZING_WITH_LIBFUZZER
#ifdef SK_BUILD_FOR_FUZZER
int safetyNet = 1000;
#endif
while ((inner = inner->next())) {
#ifdef IS_FUZZING_WITH_LIBFUZZER
#ifdef SK_BUILD_FOR_FUZZER
if (!--safetyNet) {
return false;
}

View File

@ -32,7 +32,7 @@ struct SkDCubic;
class SkTSect;
// define this when running fuzz
// #define IS_FUZZING_WITH_LIBFUZZER
// #define SK_BUILD_FOR_FUZZER
// fake classes to fool msvs Visual Studio 2018 Immediate Window
#define FakeClasses(a, b) \

View File

@ -23,7 +23,7 @@
static inline void sk_out_of_memory(size_t size) {
SK_DEBUGFAILF("sk_out_of_memory (asked for %zu bytes)",
size);
#if defined(IS_FUZZING_WITH_AFL)
#if defined(SK_BUILD_FOR_AFL_FUZZ)
exit(1);
#else
abort();
@ -54,7 +54,7 @@ void sk_abort_no_print() {
void sk_out_of_memory(void) {
SkDEBUGFAIL("sk_out_of_memory");
#if defined(IS_FUZZING_WITH_AFL)
#if defined(SK_BUILD_FOR_AFL_FUZZ)
exit(1);
#else
abort();