mirror of
https://gitlab.gnome.org/GNOME/libxml2.git
synced 2025-10-24 13:33:01 +03:00
Implement a custom mutator that takes a list of fixed-size chunks which are mutated with a given probability. This makes sure that values like parser options or failure position are mutated regularly even as the fuzz data grows large. Values can also be adjusted temporarily to make the fuzzer focus on failure injection, for example. Thanks to David Kilzer for the idea.
265 lines
7.6 KiB
C
265 lines
7.6 KiB
C
/*
|
|
* xml.c: a libFuzzer target to test several XML parser interfaces.
|
|
*
|
|
* See Copyright for the status of this software.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <libxml/catalog.h>
|
|
#include <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
#include <libxml/xmlerror.h>
|
|
#include <libxml/xmlsave.h>
|
|
#include "fuzz.h"
|
|
|
|
int
|
|
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
|
|
char ***argv ATTRIBUTE_UNUSED) {
|
|
xmlFuzzMemSetup();
|
|
xmlInitParser();
|
|
#ifdef LIBXML_CATALOG_ENABLED
|
|
xmlInitializeCatalog();
|
|
xmlCatalogSetDefaults(XML_CATA_ALLOW_NONE);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
LLVMFuzzerTestOneInput(const char *data, size_t size) {
|
|
xmlParserCtxtPtr ctxt;
|
|
xmlDocPtr doc;
|
|
const char *docBuffer, *docUrl;
|
|
size_t failurePos, docSize, maxChunkSize;
|
|
int opts;
|
|
int errorCode;
|
|
#ifdef LIBXML_OUTPUT_ENABLED
|
|
xmlBufferPtr outbuf = NULL;
|
|
const char *saveEncoding;
|
|
int saveOpts;
|
|
#endif
|
|
|
|
xmlFuzzDataInit(data, size);
|
|
opts = (int) xmlFuzzReadInt(4);
|
|
/*
|
|
* Disable options that are known to cause timeouts
|
|
*/
|
|
opts &= ~XML_PARSE_XINCLUDE &
|
|
~XML_PARSE_DTDVALID &
|
|
~XML_PARSE_SAX1;
|
|
failurePos = xmlFuzzReadInt(4) % (size + 100);
|
|
|
|
maxChunkSize = xmlFuzzReadInt(4) % (size + size / 8 + 1);
|
|
if (maxChunkSize == 0)
|
|
maxChunkSize = 1;
|
|
|
|
#ifdef LIBXML_OUTPUT_ENABLED
|
|
/* TODO: Take from fuzz data */
|
|
saveOpts = 0;
|
|
saveEncoding = NULL;
|
|
#endif
|
|
|
|
xmlFuzzReadEntities();
|
|
docBuffer = xmlFuzzMainEntity(&docSize);
|
|
docUrl = xmlFuzzMainUrl();
|
|
if (docBuffer == NULL)
|
|
goto exit;
|
|
|
|
/* Pull parser */
|
|
|
|
xmlFuzzInjectFailure(failurePos);
|
|
ctxt = xmlNewParserCtxt();
|
|
if (ctxt == NULL) {
|
|
errorCode = XML_ERR_NO_MEMORY;
|
|
} else {
|
|
xmlCtxtSetErrorHandler(ctxt, xmlFuzzSErrorFunc, NULL);
|
|
xmlCtxtSetResourceLoader(ctxt, xmlFuzzResourceLoader, NULL);
|
|
doc = xmlCtxtReadMemory(ctxt, docBuffer, docSize, docUrl, NULL, opts);
|
|
errorCode = ctxt->errNo;
|
|
xmlFuzzCheckFailureReport("xmlCtxtReadMemory",
|
|
doc == NULL && errorCode == XML_ERR_NO_MEMORY,
|
|
doc == NULL && errorCode == XML_IO_EIO);
|
|
|
|
if (doc != NULL) {
|
|
#ifdef LIBXML_OUTPUT_ENABLED
|
|
xmlSaveCtxtPtr save;
|
|
|
|
outbuf = xmlBufferCreate();
|
|
|
|
/* Also test the serializer. */
|
|
save = xmlSaveToBuffer(outbuf, saveEncoding, saveOpts);
|
|
|
|
if (save == NULL) {
|
|
xmlBufferFree(outbuf);
|
|
outbuf = NULL;
|
|
} else {
|
|
int saveErr;
|
|
|
|
xmlSaveDoc(save, doc);
|
|
saveErr = xmlSaveFinish(save);
|
|
xmlFuzzCheckFailureReport("xmlSaveToBuffer",
|
|
saveErr == XML_ERR_NO_MEMORY,
|
|
saveErr == XML_IO_EIO);
|
|
if (saveErr != XML_ERR_OK) {
|
|
xmlBufferFree(outbuf);
|
|
outbuf = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
xmlFreeDoc(doc);
|
|
}
|
|
|
|
xmlFreeParserCtxt(ctxt);
|
|
}
|
|
|
|
/* Push parser */
|
|
|
|
#ifdef LIBXML_PUSH_ENABLED
|
|
xmlFuzzInjectFailure(failurePos);
|
|
/*
|
|
* FIXME: xmlCreatePushParserCtxt can still report OOM errors
|
|
* to stderr.
|
|
*/
|
|
xmlSetGenericErrorFunc(NULL, xmlFuzzErrorFunc);
|
|
ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, docUrl);
|
|
xmlSetGenericErrorFunc(NULL, NULL);
|
|
|
|
if (ctxt != NULL) {
|
|
size_t consumed;
|
|
int errorCodePush, numChunks, maxChunks;
|
|
|
|
xmlCtxtSetErrorHandler(ctxt, xmlFuzzSErrorFunc, NULL);
|
|
xmlCtxtSetResourceLoader(ctxt, xmlFuzzResourceLoader, NULL);
|
|
xmlCtxtUseOptions(ctxt, opts);
|
|
|
|
consumed = 0;
|
|
numChunks = 0;
|
|
maxChunks = 50 + docSize / 100;
|
|
while (numChunks == 0 ||
|
|
(consumed < docSize && numChunks < maxChunks)) {
|
|
size_t chunkSize;
|
|
int terminate;
|
|
|
|
numChunks += 1;
|
|
chunkSize = docSize - consumed;
|
|
|
|
if (numChunks < maxChunks && chunkSize > maxChunkSize) {
|
|
chunkSize = maxChunkSize;
|
|
terminate = 0;
|
|
} else {
|
|
terminate = 1;
|
|
}
|
|
|
|
xmlParseChunk(ctxt, docBuffer + consumed, chunkSize, terminate);
|
|
consumed += chunkSize;
|
|
}
|
|
|
|
errorCodePush = ctxt->errNo;
|
|
xmlFuzzCheckFailureReport("xmlParseChunk",
|
|
errorCodePush == XML_ERR_NO_MEMORY,
|
|
errorCodePush == XML_IO_EIO);
|
|
doc = ctxt->myDoc;
|
|
|
|
/*
|
|
* Push and pull parser differ in when exactly they
|
|
* stop parsing, and the error code is the *last* error
|
|
* reported, so we can't check whether the codes match.
|
|
*/
|
|
if (errorCode != XML_ERR_NO_MEMORY &&
|
|
errorCode != XML_IO_EIO &&
|
|
errorCodePush != XML_ERR_NO_MEMORY &&
|
|
errorCodePush != XML_IO_EIO &&
|
|
(errorCode == XML_ERR_OK) != (errorCodePush == XML_ERR_OK)) {
|
|
fprintf(stderr, "pull/push parser error mismatch: %d != %d\n",
|
|
errorCode, errorCodePush);
|
|
#if 0
|
|
FILE *f = fopen("c.xml", "wb");
|
|
fwrite(docBuffer, docSize, 1, f);
|
|
fclose(f);
|
|
#endif
|
|
abort();
|
|
}
|
|
|
|
#ifdef LIBXML_OUTPUT_ENABLED
|
|
/*
|
|
* Verify that pull and push parser produce the same result.
|
|
*
|
|
* The NOBLANKS option doesn't work reliably in push mode.
|
|
*/
|
|
if ((opts & XML_PARSE_NOBLANKS) == 0 &&
|
|
errorCode == XML_ERR_OK &&
|
|
errorCodePush == XML_ERR_OK &&
|
|
outbuf != NULL) {
|
|
xmlBufferPtr outbufPush;
|
|
xmlSaveCtxtPtr save;
|
|
|
|
outbufPush = xmlBufferCreate();
|
|
|
|
save = xmlSaveToBuffer(outbufPush, saveEncoding, saveOpts);
|
|
|
|
if (save != NULL) {
|
|
int saveErr;
|
|
|
|
xmlSaveDoc(save, doc);
|
|
saveErr = xmlSaveFinish(save);
|
|
|
|
if (saveErr == XML_ERR_OK) {
|
|
int outbufSize = xmlBufferLength(outbuf);
|
|
|
|
if (outbufSize != xmlBufferLength(outbufPush) ||
|
|
memcmp(xmlBufferContent(outbuf),
|
|
xmlBufferContent(outbufPush),
|
|
outbufSize) != 0) {
|
|
fprintf(stderr, "pull/push parser roundtrip "
|
|
"mismatch\n");
|
|
#if 0
|
|
FILE *f = fopen("c.xml", "wb");
|
|
fwrite(docBuffer, docSize, 1, f);
|
|
fclose(f);
|
|
fprintf(stderr, "opts: %X\n", opts);
|
|
fprintf(stderr, "---\n%s\n---\n%s\n---\n",
|
|
xmlBufferContent(outbuf),
|
|
xmlBufferContent(outbufPush));
|
|
#endif
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
xmlBufferFree(outbufPush);
|
|
}
|
|
#endif
|
|
|
|
xmlFreeDoc(doc);
|
|
xmlFreeParserCtxt(ctxt);
|
|
}
|
|
#endif
|
|
|
|
exit:
|
|
#ifdef LIBXML_OUTPUT_ENABLED
|
|
xmlBufferFree(outbuf);
|
|
#endif
|
|
xmlFuzzInjectFailure(0);
|
|
xmlFuzzDataCleanup();
|
|
xmlResetLastError();
|
|
return(0);
|
|
}
|
|
|
|
size_t
|
|
LLVMFuzzerCustomMutator(char *data, size_t size, size_t maxSize,
|
|
unsigned seed) {
|
|
static const xmlFuzzChunkDesc chunks[] = {
|
|
{ 4, XML_FUZZ_PROB_ONE / 10 }, /* opts */
|
|
{ 4, XML_FUZZ_PROB_ONE / 10 }, /* failurePos */
|
|
{ 4, XML_FUZZ_PROB_ONE / 10 }, /* maxChunkSize */
|
|
{ 0, 0 }
|
|
};
|
|
|
|
return xmlFuzzMutateChunks(chunks, data, size, maxSize, seed,
|
|
LLVMFuzzerMutate);
|
|
}
|
|
|