blob: b5dfa185fe94c484cc5adaaba29baf92b36eacfe [file] [log] [blame]
/*
* fuzz.c: Common functions for fuzzing.
*
* See Copyright for the status of this software.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <libxml/hash.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/tree.h>
#include <libxml/xmlIO.h>
#include "fuzz.h"
typedef struct {
const char *data;
size_t size;
} xmlFuzzEntityInfo;
/* Single static instance for now */
static struct {
/* Original data */
const char *data;
size_t size;
/* Remaining data */
const char *ptr;
size_t remaining;
/* Buffer for unescaped strings */
char *outBuf;
char *outPtr; /* Free space at end of buffer */
xmlHashTablePtr entities; /* Maps URLs to xmlFuzzEntityInfos */
/* The first entity is the main entity. */
const char *mainUrl;
xmlFuzzEntityInfo *mainEntity;
} fuzzData;
/**
* xmlFuzzErrorFunc:
*
* An error function that simply discards all errors.
*/
void
xmlFuzzErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED,
...) {
}
/**
* xmlFuzzDataInit:
*
* Initialize fuzz data provider.
*/
void
xmlFuzzDataInit(const char *data, size_t size) {
fuzzData.data = data;
fuzzData.size = size;
fuzzData.ptr = data;
fuzzData.remaining = size;
fuzzData.outBuf = xmlMalloc(size + 1);
fuzzData.outPtr = fuzzData.outBuf;
fuzzData.entities = xmlHashCreate(8);
fuzzData.mainUrl = NULL;
fuzzData.mainEntity = NULL;
}
/**
* xmlFuzzDataFree:
*
* Cleanup fuzz data provider.
*/
void
xmlFuzzDataCleanup(void) {
xmlFree(fuzzData.outBuf);
xmlHashFree(fuzzData.entities, xmlHashDefaultDeallocator);
}
/**
* xmlFuzzReadInt:
* @size: size of string in bytes
*
* Read an integer from the fuzz data.
*/
int
xmlFuzzReadInt() {
int ret;
if (fuzzData.remaining < sizeof(int))
return(0);
memcpy(&ret, fuzzData.ptr, sizeof(int));
fuzzData.ptr += sizeof(int);
fuzzData.remaining -= sizeof(int);
return ret;
}
/**
* xmlFuzzReadRemaining:
* @size: size of string in bytes
*
* Read remaining bytes from fuzz data.
*/
const char *
xmlFuzzReadRemaining(size_t *size) {
const char *ret = fuzzData.ptr;
*size = fuzzData.remaining;
fuzzData.ptr += fuzzData.remaining;
fuzzData.remaining = 0;
return(ret);
}
/*
* xmlFuzzWriteString:
* @out: output file
* @str: string to write
*
* Write a random-length string to file in a format similar to
* FuzzedDataProvider. Backslash followed by newline marks the end of the
* string. Two backslashes are used to escape a backslash.
*/
void
xmlFuzzWriteString(FILE *out, const char *str) {
for (; *str; str++) {
int c = (unsigned char) *str;
putc(c, out);
if (c == '\\')
putc(c, out);
}
putc('\\', out);
putc('\n', out);
}
/**
* xmlFuzzReadString:
* @size: size of string in bytes
*
* Read a random-length string from the fuzz data.
*
* The format is similar to libFuzzer's FuzzedDataProvider but treats
* backslash followed by newline as end of string. This makes the fuzz data
* more readable. A backslash character is escaped with another backslash.
*
* Returns a zero-terminated string or NULL if the fuzz data is exhausted.
*/
const char *
xmlFuzzReadString(size_t *size) {
const char *out = fuzzData.outPtr;
while (fuzzData.remaining > 0) {
int c = *fuzzData.ptr++;
fuzzData.remaining--;
if ((c == '\\') && (fuzzData.remaining > 0)) {
int c2 = *fuzzData.ptr;
if (c2 == '\n') {
fuzzData.ptr++;
fuzzData.remaining--;
*size = fuzzData.outPtr - out;
*fuzzData.outPtr++ = '\0';
return(out);
}
if (c2 == '\\') {
fuzzData.ptr++;
fuzzData.remaining--;
}
}
*fuzzData.outPtr++ = c;
}
if (fuzzData.outPtr > out) {
*size = fuzzData.outPtr - out;
*fuzzData.outPtr++ = '\0';
return(out);
}
return(NULL);
}
/**
* xmlFuzzReadEntities:
*
* Read entities like the main XML file, external DTDs, external parsed
* entities from fuzz data.
*/
void
xmlFuzzReadEntities(void) {
size_t num = 0;
while (1) {
const char *url, *entity;
size_t urlSize, entitySize;
xmlFuzzEntityInfo *entityInfo;
url = xmlFuzzReadString(&urlSize);
if (url == NULL) break;
entity = xmlFuzzReadString(&entitySize);
if (entity == NULL) break;
if (xmlHashLookup(fuzzData.entities, (xmlChar *)url) == NULL) {
entityInfo = xmlMalloc(sizeof(xmlFuzzEntityInfo));
entityInfo->data = entity;
entityInfo->size = entitySize;
xmlHashAddEntry(fuzzData.entities, (xmlChar *)url, entityInfo);
if (num == 0) {
fuzzData.mainUrl = url;
fuzzData.mainEntity = entityInfo;
}
num++;
}
}
}
/**
* xmlFuzzMainUrl:
*
* Returns the main URL.
*/
const char *
xmlFuzzMainUrl(void) {
return(fuzzData.mainUrl);
}
/**
* xmlFuzzMainEntity:
* @size: size of the main entity in bytes
*
* Returns the main entity.
*/
const char *
xmlFuzzMainEntity(size_t *size) {
if (fuzzData.mainEntity == NULL)
return(NULL);
*size = fuzzData.mainEntity->size;
return(fuzzData.mainEntity->data);
}
/**
* xmlFuzzEntityLoader:
*
* The entity loader for fuzz data.
*/
xmlParserInputPtr
xmlFuzzEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED,
xmlParserCtxtPtr ctxt) {
xmlParserInputPtr input;
xmlFuzzEntityInfo *entity;
if (URL == NULL)
return(NULL);
entity = xmlHashLookup(fuzzData.entities, (xmlChar *) URL);
if (entity == NULL)
return(NULL);
input = xmlNewInputStream(ctxt);
input->filename = NULL;
input->buf = xmlParserInputBufferCreateMem(entity->data, entity->size,
XML_CHAR_ENCODING_NONE);
input->base = input->cur = xmlBufContent(input->buf->buffer);
input->end = input->base + entity->size;
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;
struct stat statbuf;
char *data;
size_t size;
if ((stat(path, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode)))
return(NULL);
size = statbuf.st_size;
file = fopen(path, "rb");
if (file == NULL)
return(NULL);
data = xmlMalloc(size + 1);
if (data != NULL) {
if (fread(data, 1, size, file) != size) {
xmlFree(data);
data = NULL;
} else {
data[size] = 0;
if (sizeRet != NULL)
*sizeRet = size;
}
}
fclose(file);
return(data);
}