fuzz: Inject random malloc failures

Fixes #344.
This commit is contained in:
Nick Wellnhofer 2023-03-08 13:59:03 +01:00
parent 7cd2676277
commit 42322eba82
10 changed files with 187 additions and 108 deletions

View File

@ -42,6 +42,9 @@ static struct {
xmlFuzzEntityInfo *mainEntity;
} fuzzData;
size_t fuzzNumAllocs;
size_t fuzzMaxAllocs;
/**
* xmlFuzzErrorFunc:
*
@ -52,6 +55,58 @@ xmlFuzzErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED,
...) {
}
/*
* Malloc failure injection.
*
* Quick tip to debug complicated issues: Increase MALLOC_OFFSET until
* the crash disappears (or a different issue is triggered). Then set
* the offset to the highest value that produces a crash and set
* MALLOC_ABORT to 1 to see which failed memory allocation causes the
* issue.
*/
#define XML_FUZZ_MALLOC_OFFSET 0
#define XML_FUZZ_MALLOC_ABORT 0
static void *
xmlFuzzMalloc(size_t size) {
if (fuzzMaxAllocs > 0) {
if (fuzzNumAllocs >= fuzzMaxAllocs - 1)
#if XML_FUZZ_MALLOC_ABORT
abort();
#else
return(NULL);
#endif
fuzzNumAllocs += 1;
}
return malloc(size);
}
static void *
xmlFuzzRealloc(void *ptr, size_t size) {
if (fuzzMaxAllocs > 0) {
if (fuzzNumAllocs >= fuzzMaxAllocs - 1)
#if XML_FUZZ_MALLOC_ABORT
abort();
#else
return(NULL);
#endif
fuzzNumAllocs += 1;
}
return realloc(ptr, size);
}
void
xmlFuzzMemSetup(void) {
xmlMemSetup(free, xmlFuzzMalloc, xmlFuzzRealloc, xmlMemStrdup);
}
void
xmlFuzzMemSetLimit(size_t limit) {
fuzzNumAllocs = 0;
fuzzMaxAllocs = limit ? limit + XML_FUZZ_MALLOC_OFFSET : 0;
}
/**
* xmlFuzzDataInit:
*
@ -299,6 +354,8 @@ xmlFuzzEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED,
return(NULL);
input = xmlNewInputStream(ctxt);
if (input == NULL)
return(NULL);
input->filename = (char *) xmlCharStrdup(URL);
input->buf = xmlParserInputBufferCreateMem(entity->data, entity->size,
XML_CHAR_ENCODING_NONE);
@ -312,47 +369,6 @@ xmlFuzzEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED,
return input;
}
/**
* xmlFuzzExtractStrings:
*
* Extract C strings from input data. Use exact-size allocations to detect
* potential memory errors.
*/
size_t
xmlFuzzExtractStrings(const char *data, size_t size, char **strings,
size_t numStrings) {
const char *start = data;
const char *end = data + size;
size_t i = 0, ret;
while (i < numStrings) {
size_t strSize = end - start;
const char *zero = memchr(start, 0, strSize);
if (zero != NULL)
strSize = zero - start;
strings[i] = xmlMalloc(strSize + 1);
memcpy(strings[i], start, strSize);
strings[i][strSize] = '\0';
i++;
if (zero != NULL)
start = zero + 1;
else
break;
}
ret = i;
while (i < numStrings) {
strings[i] = NULL;
i++;
}
return(ret);
}
char *
xmlSlurpFile(const char *path, size_t *sizeRet) {
FILE *file;

View File

@ -49,6 +49,12 @@ void
xmlFuzzErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED,
...);
void
xmlFuzzMemSetup(void);
void
xmlFuzzMemSetLimit(size_t limit);
void
xmlFuzzDataInit(const char *data, size_t size);
@ -82,10 +88,6 @@ xmlFuzzMainEntity(size_t *size);
xmlParserInputPtr
xmlFuzzEntityLoader(const char *URL, const char *ID, xmlParserCtxtPtr ctxt);
size_t
xmlFuzzExtractStrings(const char *data, size_t size, char **strings,
size_t numStrings);
char *
xmlSlurpFile(const char *path, size_t *size);

View File

@ -114,6 +114,8 @@ processXml(const char *docFile, FILE *out) {
/* Parser options. */
xmlFuzzWriteInt(out, opts, 4);
/* Max allocations. */
xmlFuzzWriteInt(out, 0, 4);
fuzzRecorderInit(out);
@ -136,6 +138,8 @@ processHtml(const char *docFile, FILE *out) {
/* Parser options. */
xmlFuzzWriteInt(out, 0, 4);
/* Max allocations. */
xmlFuzzWriteInt(out, 0, 4);
/* Copy file */
file = fopen(docFile, "rb");
@ -160,6 +164,9 @@ processSchema(const char *docFile, FILE *out) {
xmlSchemaPtr schema;
xmlSchemaParserCtxtPtr pctxt;
/* Max allocations. */
xmlFuzzWriteInt(out, 0, 4);
fuzzRecorderInit(out);
pctxt = xmlSchemaNewParserCtxt(docFile);
@ -314,6 +321,9 @@ processXPath(const char *testDir, const char *prefix, const char *name,
continue;
}
/* Max allocations. */
xmlFuzzWriteInt(out, 0, 4);
if (xptr) {
xmlFuzzWriteString(out, expr);
} else {

View File

@ -12,6 +12,7 @@
int
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
char ***argv ATTRIBUTE_UNUSED) {
xmlFuzzMemSetup();
xmlInitParser();
#ifdef LIBXML_CATALOG_ENABLED
xmlInitializeCatalog();
@ -28,11 +29,12 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
htmlParserCtxtPtr ctxt;
xmlOutputBufferPtr out;
const char *docBuffer;
size_t docSize, consumed, chunkSize;
size_t maxAlloc, docSize, consumed, chunkSize;
int opts;
xmlFuzzDataInit(data, size);
opts = (int) xmlFuzzReadInt(4);
maxAlloc = xmlFuzzReadInt(4) % (size + 1);
docBuffer = xmlFuzzReadRemaining(&docSize);
if (docBuffer == NULL) {
@ -42,6 +44,7 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
/* Pull parser */
xmlFuzzMemSetLimit(maxAlloc);
doc = htmlReadMemory(docBuffer, docSize, NULL, NULL, opts);
/*
@ -57,23 +60,28 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
/* Push parser */
xmlFuzzMemSetLimit(maxAlloc);
ctxt = htmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL,
XML_CHAR_ENCODING_NONE);
htmlCtxtUseOptions(ctxt, opts);
for (consumed = 0; consumed < docSize; consumed += chunkSize) {
chunkSize = docSize - consumed;
if (chunkSize > maxChunkSize)
chunkSize = maxChunkSize;
htmlParseChunk(ctxt, docBuffer + consumed, chunkSize, 0);
if (ctxt != NULL) {
htmlCtxtUseOptions(ctxt, opts);
for (consumed = 0; consumed < docSize; consumed += chunkSize) {
chunkSize = docSize - consumed;
if (chunkSize > maxChunkSize)
chunkSize = maxChunkSize;
htmlParseChunk(ctxt, docBuffer + consumed, chunkSize, 0);
}
htmlParseChunk(ctxt, NULL, 0, 1);
xmlFreeDoc(ctxt->myDoc);
htmlFreeParserCtxt(ctxt);
}
htmlParseChunk(ctxt, NULL, 0, 1);
xmlFreeDoc(ctxt->myDoc);
htmlFreeParserCtxt(ctxt);
/* Cleanup */
xmlFuzzMemSetLimit(0);
xmlFuzzDataCleanup();
xmlResetLastError();

View File

@ -10,6 +10,7 @@
int
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
char ***argv ATTRIBUTE_UNUSED) {
xmlFuzzMemSetup();
xmlSetGenericErrorFunc(NULL, xmlFuzzErrorFunc);
return 0;
@ -18,27 +19,29 @@ LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
int
LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlRegexpPtr regexp;
char *str[2] = { NULL, NULL };
size_t maxAlloc;
const char *str1;
if (size > 200)
return(0);
xmlFuzzExtractStrings(data, size, str, 2);
xmlFuzzDataInit(data, size);
maxAlloc = xmlFuzzReadInt(4) % (size * 8 + 1);
str1 = xmlFuzzReadString(NULL);
/* CUR_SCHAR doesn't handle invalid UTF-8 and may cause infinite loops. */
if (xmlCheckUTF8(BAD_CAST str[0]) != 0) {
regexp = xmlRegexpCompile(BAD_CAST str[0]);
if (xmlCheckUTF8(BAD_CAST str1) != 0) {
xmlFuzzMemSetLimit(maxAlloc);
regexp = xmlRegexpCompile(BAD_CAST str1);
/* xmlRegexpExec has pathological performance in too many cases. */
#if 0
if ((regexp != NULL) && (numStrings >= 2)) {
xmlRegexpExec(regexp, BAD_CAST str[1]);
}
xmlRegexpExec(regexp, BAD_CAST str2);
#endif
xmlRegFreeRegexp(regexp);
}
xmlFree(str[0]);
xmlFree(str[1]);
xmlFuzzMemSetLimit(0);
xmlFuzzDataCleanup();
xmlResetLastError();
return 0;

View File

@ -11,6 +11,7 @@
int
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
char ***argv ATTRIBUTE_UNUSED) {
xmlFuzzMemSetup();
xmlInitParser();
#ifdef LIBXML_CATALOG_ENABLED
xmlInitializeCatalog();
@ -24,18 +25,23 @@ LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
int
LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlSchemaParserCtxtPtr pctxt;
size_t maxAlloc;
if (size > 50000)
return(0);
maxAlloc = xmlFuzzReadInt(4) % (size + 1);
xmlFuzzDataInit(data, size);
xmlFuzzReadEntities();
xmlFuzzMemSetLimit(maxAlloc);
pctxt = xmlSchemaNewParserCtxt(xmlFuzzMainUrl());
xmlSchemaSetParserErrors(pctxt, xmlFuzzErrorFunc, xmlFuzzErrorFunc, NULL);
xmlSchemaFree(xmlSchemaParse(pctxt));
xmlSchemaFreeParserCtxt(pctxt);
xmlFuzzMemSetLimit(0);
xmlFuzzDataCleanup();
xmlResetLastError();

View File

@ -8,40 +8,54 @@
#include "fuzz.h"
int
LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlURIPtr uri;
char *str[2] = { NULL, NULL };
size_t numStrings;
if (size > 10000)
return(0);
numStrings = xmlFuzzExtractStrings(data, size, str, 2);
uri = xmlParseURI(str[0]);
xmlFree(xmlSaveUri(uri));
xmlFreeURI(uri);
uri = xmlParseURIRaw(str[0], 1);
xmlFree(xmlSaveUri(uri));
xmlFreeURI(uri);
xmlFree(xmlURIUnescapeString(str[0], -1, NULL));
xmlFree(xmlURIEscape(BAD_CAST str[0]));
xmlFree(xmlCanonicPath(BAD_CAST str[0]));
xmlFree(xmlPathToURI(BAD_CAST str[0]));
if (numStrings >= 2) {
xmlFree(xmlBuildURI(BAD_CAST str[1], BAD_CAST str[0]));
xmlFree(xmlBuildRelativeURI(BAD_CAST str[1], BAD_CAST str[0]));
xmlFree(xmlURIEscapeStr(BAD_CAST str[0], BAD_CAST str[1]));
}
/* Modifies string, so must come last. */
xmlNormalizeURIPath(str[0]);
xmlFree(str[0]);
xmlFree(str[1]);
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
char ***argv ATTRIBUTE_UNUSED) {
xmlFuzzMemSetup();
xmlSetGenericErrorFunc(NULL, xmlFuzzErrorFunc);
return 0;
}
int
LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlURIPtr uri;
size_t maxAlloc;
const char *str1, *str2;
char *copy;
if (size > 10000)
return(0);
xmlFuzzDataInit(data, size);
maxAlloc = xmlFuzzReadInt(4) % (size * 8 + 1);
str1 = xmlFuzzReadString(NULL);
str2 = xmlFuzzReadString(NULL);
xmlFuzzMemSetLimit(maxAlloc);
uri = xmlParseURI(str1);
xmlFree(xmlSaveUri(uri));
xmlFreeURI(uri);
uri = xmlParseURIRaw(str1, 1);
xmlFree(xmlSaveUri(uri));
xmlFreeURI(uri);
xmlFree(xmlURIUnescapeString(str1, -1, NULL));
xmlFree(xmlURIEscape(BAD_CAST str1));
xmlFree(xmlCanonicPath(BAD_CAST str1));
xmlFree(xmlPathToURI(BAD_CAST str1));
xmlFree(xmlBuildURI(BAD_CAST str2, BAD_CAST str1));
xmlFree(xmlBuildRelativeURI(BAD_CAST str2, BAD_CAST str1));
xmlFree(xmlURIEscapeStr(BAD_CAST str1, BAD_CAST str2));
copy = (char *) xmlCharStrdup(str1);
xmlNormalizeURIPath(copy);
xmlFree(copy);
xmlFuzzMemSetLimit(0);
xmlFuzzDataCleanup();
return 0;
}

View File

@ -15,6 +15,7 @@
int
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
char ***argv ATTRIBUTE_UNUSED) {
xmlFuzzMemSetup();
xmlInitParser();
#ifdef LIBXML_CATALOG_ENABLED
xmlInitializeCatalog();
@ -30,12 +31,13 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlDocPtr doc;
xmlTextReaderPtr reader;
const char *docBuffer, *docUrl;
size_t docSize;
size_t maxAlloc, docSize;
int opts;
xmlFuzzDataInit(data, size);
opts = (int) xmlFuzzReadInt(4);
opts |= XML_PARSE_XINCLUDE;
maxAlloc = xmlFuzzReadInt(4) % (size + 1);
xmlFuzzReadEntities();
docBuffer = xmlFuzzMainEntity(&docSize);
@ -45,12 +47,14 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
/* Pull parser */
xmlFuzzMemSetLimit(maxAlloc);
doc = xmlReadMemory(docBuffer, docSize, docUrl, NULL, opts);
xmlXIncludeProcessFlags(doc, opts);
xmlFreeDoc(doc);
/* Reader */
xmlFuzzMemSetLimit(maxAlloc);
reader = xmlReaderForMemory(docBuffer, docSize, NULL, NULL, opts);
if (reader == NULL)
goto exit;
@ -66,6 +70,7 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlFreeTextReader(reader);
exit:
xmlFuzzMemSetLimit(0);
xmlFuzzDataCleanup();
xmlResetLastError();
return(0);

View File

@ -14,6 +14,7 @@
int
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
char ***argv ATTRIBUTE_UNUSED) {
xmlFuzzMemSetup();
xmlInitParser();
#ifdef LIBXML_CATALOG_ENABLED
xmlInitializeCatalog();
@ -32,12 +33,13 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlTextReaderPtr reader;
xmlChar *out;
const char *docBuffer, *docUrl;
size_t docSize, consumed, chunkSize;
size_t maxAlloc, docSize, consumed, chunkSize;
int opts, outSize;
xmlFuzzDataInit(data, size);
opts = (int) xmlFuzzReadInt(4);
opts &= ~XML_PARSE_XINCLUDE;
maxAlloc = xmlFuzzReadInt(4) % (size + 1);
xmlFuzzReadEntities();
docBuffer = xmlFuzzMainEntity(&docSize);
@ -47,6 +49,7 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
/* Pull parser */
xmlFuzzMemSetLimit(maxAlloc);
doc = xmlReadMemory(docBuffer, docSize, docUrl, NULL, opts);
/* Also test the serializer. */
xmlDocDumpMemory(doc, &out, &outSize);
@ -55,6 +58,7 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
/* Push parser */
xmlFuzzMemSetLimit(maxAlloc);
ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, docUrl);
if (ctxt == NULL)
goto exit;
@ -73,6 +77,7 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
/* Reader */
xmlFuzzMemSetLimit(maxAlloc);
reader = xmlReaderForMemory(docBuffer, docSize, NULL, NULL, opts);
if (reader == NULL)
goto exit;
@ -88,6 +93,7 @@ LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlFreeTextReader(reader);
exit:
xmlFuzzMemSetLimit(0);
xmlFuzzDataCleanup();
xmlResetLastError();
return(0);

View File

@ -11,6 +11,7 @@
int
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
char ***argv ATTRIBUTE_UNUSED) {
xmlFuzzMemSetup();
xmlInitParser();
xmlSetGenericErrorFunc(NULL, xmlFuzzErrorFunc);
@ -21,28 +22,36 @@ int
LLVMFuzzerTestOneInput(const char *data, size_t size) {
xmlDocPtr doc;
const char *expr, *xml;
size_t exprSize, xmlSize;
size_t maxAlloc, exprSize, xmlSize;
if (size > 10000)
return(0);
xmlFuzzDataInit(data, size);
maxAlloc = xmlFuzzReadInt(4) % (size + 1);
expr = xmlFuzzReadString(&exprSize);
xml = xmlFuzzReadString(&xmlSize);
/* Recovery mode allows more input to be fuzzed. */
doc = xmlReadMemory(xml, xmlSize, NULL, NULL, XML_PARSE_RECOVER);
if (doc != NULL) {
xmlXPathContextPtr xpctxt = xmlXPathNewContext(doc);
xmlXPathContextPtr xpctxt;
/* Operation limit to avoid timeout */
xpctxt->opLimit = 500000;
xmlFuzzMemSetLimit(maxAlloc);
xmlXPathFreeObject(xmlXPtrEval(BAD_CAST expr, xpctxt));
xmlXPathFreeContext(xpctxt);
xpctxt = xmlXPathNewContext(doc);
if (xpctxt != NULL) {
/* Operation limit to avoid timeout */
xpctxt->opLimit = 500000;
xmlXPathFreeObject(xmlXPtrEval(BAD_CAST expr, xpctxt));
xmlXPathFreeContext(xpctxt);
}
xmlFuzzMemSetLimit(0);
xmlFreeDoc(doc);
}
xmlFreeDoc(doc);
xmlFuzzDataCleanup();
xmlResetLastError();