1
0
mirror of https://gitlab.gnome.org/GNOME/libxml2.git synced 2026-01-26 21:41:34 +03:00
Files
libxml2/fuzz/xml.c
Nick Wellnhofer 40e423d6c2 fuzz: Improve fuzzing of push parser
Also serialize the result of push-parsing and compare whether pull and
push parser produce the same result (differential fuzzing).

We lose the ability to inject IO errors when serializing for now, but
this isn't too important.

Use variable chunk size for push parser.

Fixes #849.
2025-01-31 15:50:00 +01:00

251 lines
7.2 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 + 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);
}