615 lines
15 KiB
C
615 lines
15 KiB
C
/*
|
||
* zzuf - general purpose fuzzer
|
||
* Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
|
||
* All Rights Reserved
|
||
*
|
||
* $Id$
|
||
*
|
||
* This program is free software. It comes without any warranty, to
|
||
* the extent permitted by applicable law. You can redistribute it
|
||
* and/or modify it under the terms of the Do What The Fuck You Want
|
||
* To Public License, Version 2, as published by Sam Hocevar. See
|
||
* http://sam.zoy.org/wtfpl/COPYING for more details.
|
||
*/
|
||
|
||
/*
|
||
* load-stream.c: loaded stream functions
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#define _GNU_SOURCE /* for getline() and getdelim() */
|
||
|
||
#if defined HAVE_STDINT_H
|
||
# include <stdint.h>
|
||
#elif defined HAVE_INTTYPES_H
|
||
# include <inttypes.h>
|
||
#endif
|
||
#include <stdlib.h>
|
||
|
||
#include <stdio.h>
|
||
#include <sys/types.h>
|
||
#if defined HAVE___SREFILL && defined HAVE_UNISTD_H
|
||
# include <unistd.h> /* Needed for __srefill’s lseek() call */
|
||
#endif
|
||
|
||
#include "libzzuf.h"
|
||
#include "lib-load.h"
|
||
#include "debug.h"
|
||
#include "fuzz.h"
|
||
#include "fd.h"
|
||
|
||
#if defined HAVE___SREFILL
|
||
int NEW(__srefill)(FILE *fp);
|
||
#endif
|
||
|
||
/* Library functions that we divert */
|
||
static FILE * (*ORIG(fopen)) (const char *path, const char *mode);
|
||
#if defined HAVE_FOPEN64
|
||
static FILE * (*ORIG(fopen64)) (const char *path, const char *mode);
|
||
#endif
|
||
static FILE * (*ORIG(freopen)) (const char *path, const char *mode,
|
||
FILE *stream);
|
||
static int (*ORIG(fseek)) (FILE *stream, long offset, int whence);
|
||
#if defined HAVE_FSEEKO
|
||
static int (*ORIG(fseeko)) (FILE *stream, off_t offset, int whence);
|
||
#endif
|
||
static void (*ORIG(rewind)) (FILE *stream);
|
||
static size_t (*ORIG(fread)) (void *ptr, size_t size, size_t nmemb,
|
||
FILE *stream);
|
||
static int (*ORIG(getc)) (FILE *stream);
|
||
static int (*ORIG(fgetc)) (FILE *stream);
|
||
#if defined HAVE__IO_GETC
|
||
static int (*ORIG(_IO_getc)) (FILE *stream);
|
||
#endif
|
||
#if defined HAVE_GETC_UNLOCKED
|
||
static int (*ORIG(getc_unlocked)) (FILE *stream);
|
||
#endif
|
||
#if defined HAVE_FGETC_UNLOCKED
|
||
static int (*ORIG(fgetc_unlocked)) (FILE *stream);
|
||
#endif
|
||
static char * (*ORIG(fgets)) (char *s, int size, FILE *stream);
|
||
static int (*ORIG(ungetc)) (int c, FILE *stream);
|
||
static int (*ORIG(fclose)) (FILE *fp);
|
||
|
||
/* Additional GNUisms */
|
||
#if defined HAVE_GETLINE
|
||
static ssize_t (*ORIG(getline)) (char **lineptr, size_t *n, FILE *stream);
|
||
#endif
|
||
#if defined HAVE_GETDELIM
|
||
static ssize_t (*ORIG(getdelim)) (char **lineptr, size_t *n, int delim,
|
||
FILE *stream);
|
||
#endif
|
||
#if defined HAVE___GETDELIM
|
||
static ssize_t (*ORIG(__getdelim)) (char **lineptr, size_t *n, int delim,
|
||
FILE *stream);
|
||
#endif
|
||
|
||
/* Additional BSDisms */
|
||
#if defined HAVE_FGETLN
|
||
static char * (*ORIG(fgetln)) (FILE *stream, size_t *len);
|
||
#endif
|
||
#if defined HAVE___SREFILL
|
||
int (*ORIG(__srefill)) (FILE *fp);
|
||
#endif
|
||
|
||
/* Our function wrappers */
|
||
#define FOPEN(fn) \
|
||
do \
|
||
{ \
|
||
LOADSYM(fn); \
|
||
if(!_zz_ready) \
|
||
return ORIG(fn)(path, mode); \
|
||
_zz_lock(-1); \
|
||
ret = ORIG(fn)(path, mode); \
|
||
_zz_unlock(-1); \
|
||
if(ret && _zz_mustwatch(path)) \
|
||
{ \
|
||
int fd = fileno(ret); \
|
||
_zz_register(fd); \
|
||
debug("%s(\"%s\", \"%s\") = [%i]", __func__, path, mode, fd); \
|
||
} \
|
||
} while(0)
|
||
|
||
FILE *NEW(fopen)(const char *path, const char *mode)
|
||
{
|
||
FILE *ret; FOPEN(fopen); return ret;
|
||
}
|
||
|
||
#if defined HAVE_FOPEN64
|
||
FILE *NEW(fopen64)(const char *path, const char *mode)
|
||
{
|
||
FILE *ret; FOPEN(fopen64); return ret;
|
||
}
|
||
#endif
|
||
|
||
FILE *NEW(freopen)(const char *path, const char *mode, FILE *stream)
|
||
{
|
||
FILE *ret;
|
||
int fd0 = -1, fd1 = -1, disp = 0;
|
||
|
||
LOADSYM(freopen);
|
||
if(_zz_ready && (fd0 = fileno(stream)) >= 0 && _zz_iswatched(fd0))
|
||
{
|
||
_zz_unregister(fd0);
|
||
disp = 1;
|
||
}
|
||
|
||
_zz_lock(-1);
|
||
ret = ORIG(freopen)(path, mode, stream);
|
||
_zz_unlock(-1);
|
||
|
||
if(ret && _zz_mustwatch(path))
|
||
{
|
||
fd1 = fileno(ret);
|
||
_zz_register(fd1);
|
||
disp = 1;
|
||
}
|
||
|
||
if(disp)
|
||
debug("%s(\"%s\", \"%s\", [%i]) = [%i]", __func__,
|
||
path, mode, fd0, fd1);
|
||
|
||
return ret;
|
||
}
|
||
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
# define FSEEK_FUZZ(fn2)
|
||
#else
|
||
# define FSEEK_FUZZ(fn2) \
|
||
if(ret == 0) \
|
||
{ \
|
||
/* FIXME: check what happens when fseek()ing a pipe */ \
|
||
switch(whence) \
|
||
{ \
|
||
case SEEK_END: \
|
||
offset = fn2(stream); \
|
||
/* fall through */ \
|
||
case SEEK_SET: \
|
||
_zz_setpos(fd, offset); \
|
||
break; \
|
||
case SEEK_CUR: \
|
||
_zz_addpos(fd, offset); \
|
||
break; \
|
||
} \
|
||
}
|
||
#endif
|
||
|
||
#define FSEEK(fn, fn2) \
|
||
do \
|
||
{ \
|
||
int fd; \
|
||
LOADSYM(fn); \
|
||
fd = fileno(stream); \
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
|
||
return ORIG(fn)(stream, offset, whence); \
|
||
_zz_lock(fd); \
|
||
ret = ORIG(fn)(stream, offset, whence); \
|
||
_zz_unlock(fd); \
|
||
debug("%s([%i], %lli, %i) = %i", __func__, \
|
||
fd, (long long int)offset, whence, ret); \
|
||
FSEEK_FUZZ(fn2) \
|
||
} while(0)
|
||
|
||
int NEW(fseek)(FILE *stream, long offset, int whence)
|
||
{
|
||
int ret; FSEEK(fseek, ftell); return ret;
|
||
}
|
||
|
||
#if defined HAVE_FSEEKO
|
||
int NEW(fseeko)(FILE *stream, off_t offset, int whence)
|
||
{
|
||
int ret; FSEEK(fseeko, ftello); return ret;
|
||
}
|
||
#endif
|
||
|
||
void NEW(rewind)(FILE *stream)
|
||
{
|
||
int fd;
|
||
|
||
LOADSYM(rewind);
|
||
fd = fileno(stream);
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
|
||
{
|
||
ORIG(rewind)(stream);
|
||
return;
|
||
}
|
||
|
||
_zz_lock(fd);
|
||
ORIG(rewind)(stream);
|
||
_zz_unlock(fd);
|
||
debug("%s([%i])", __func__, fd);
|
||
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
#else
|
||
/* FIXME: check what happens when rewind()ing a pipe */
|
||
_zz_setpos(fd, 0);
|
||
#endif
|
||
}
|
||
|
||
size_t NEW(fread)(void *ptr, size_t size, size_t nmemb, FILE *stream)
|
||
{
|
||
int64_t pos;
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
#else
|
||
int64_t newpos;
|
||
#endif
|
||
size_t ret;
|
||
int fd;
|
||
|
||
LOADSYM(fread);
|
||
fd = fileno(stream);
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
|
||
return ORIG(fread)(ptr, size, nmemb, stream);
|
||
|
||
pos = ftell(stream);
|
||
_zz_lock(fd);
|
||
ret = ORIG(fread)(ptr, size, nmemb, stream);
|
||
_zz_unlock(fd);
|
||
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
#else
|
||
newpos = ftell(stream);
|
||
/* XXX: the number of bytes read is not ret * size, because
|
||
* a partial read may have advanced the stream pointer. However,
|
||
* when reading from a pipe ftell() will return 0, and ret * size
|
||
* is then better than nothing. */
|
||
if(newpos <= 0)
|
||
{
|
||
pos = _zz_getpos(fd);
|
||
newpos = pos + ret * size;
|
||
}
|
||
if(newpos != pos)
|
||
{
|
||
char *b = ptr;
|
||
|
||
_zz_fuzz(fd, ptr, newpos - pos);
|
||
_zz_setpos(fd, newpos);
|
||
|
||
if(newpos >= pos + 4)
|
||
debug("%s(%p, %li, %li, [%i]) = %li \"%c%c%c%c...", __func__, ptr,
|
||
(long int)size, (long int)nmemb, fd, (long int)ret,
|
||
b[0], b[1], b[2], b[3]);
|
||
else
|
||
debug("%s(%p, %li, %li, [%i]) = %li \"%c...", __func__, ptr,
|
||
(long int)size, (long int)nmemb, fd, (long int)ret, b[0]);
|
||
}
|
||
else
|
||
#endif
|
||
debug("%s(%p, %li, %li, [%i]) = %li", __func__, ptr,
|
||
(long int)size, (long int)nmemb, fd, (long int)ret);
|
||
|
||
return ret;
|
||
}
|
||
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
# define FGETC_FUZZ
|
||
#else
|
||
# define FGETC_FUZZ \
|
||
if(ret != EOF) \
|
||
{ \
|
||
uint8_t ch = ret; \
|
||
_zz_fuzz(fd, &ch, 1); \
|
||
_zz_addpos(fd, 1); \
|
||
ret = ch; \
|
||
}
|
||
#endif
|
||
|
||
#define FGETC(fn) \
|
||
do { \
|
||
int fd; \
|
||
LOADSYM(fn); \
|
||
fd = fileno(stream); \
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
|
||
return ORIG(fn)(stream); \
|
||
_zz_lock(fd); \
|
||
ret = ORIG(fn)(stream); \
|
||
_zz_unlock(fd); \
|
||
FGETC_FUZZ \
|
||
if(ret == EOF) \
|
||
debug("%s([%i]) = EOF", __func__, fd); \
|
||
else \
|
||
debug("%s([%i]) = '%c'", __func__, fd, ret); \
|
||
} while(0)
|
||
|
||
#undef getc /* can be a macro; we don’t want that */
|
||
int NEW(getc)(FILE *stream)
|
||
{
|
||
int ret; FGETC(getc); return ret;
|
||
}
|
||
|
||
int NEW(fgetc)(FILE *stream)
|
||
{
|
||
int ret; FGETC(fgetc); return ret;
|
||
}
|
||
|
||
#if defined HAVE__IO_GETC
|
||
int NEW(_IO_getc)(FILE *stream)
|
||
{
|
||
int ret; FGETC(_IO_getc); return ret;
|
||
}
|
||
#endif
|
||
|
||
#if defined HAVE_GETC_UNLOCKED
|
||
int NEW(getc_unlocked)(FILE *stream)
|
||
{
|
||
int ret; FGETC(getc_unlocked); return ret;
|
||
}
|
||
#endif
|
||
|
||
#if defined HAVE_FGETC_UNLOCKED
|
||
int NEW(fgetc_unlocked)(FILE *stream)
|
||
{
|
||
int ret; FGETC(fgetc_unlocked); return ret;
|
||
}
|
||
#endif
|
||
|
||
char *NEW(fgets)(char *s, int size, FILE *stream)
|
||
{
|
||
char *ret = s;
|
||
int fd;
|
||
|
||
LOADSYM(fgets);
|
||
LOADSYM(fgetc);
|
||
fd = fileno(stream);
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
|
||
return ORIG(fgets)(s, size, stream);
|
||
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
_zz_lock(fd);
|
||
ret = ORIG(fgets)(s, size, stream);
|
||
_zz_unlock(fd);
|
||
#else
|
||
if(size <= 0)
|
||
ret = NULL;
|
||
else if(size == 1)
|
||
s[0] = '\0';
|
||
else
|
||
{
|
||
int i;
|
||
|
||
for(i = 0; i < size - 1; i++)
|
||
{
|
||
int ch;
|
||
|
||
_zz_lock(fd);
|
||
ch = ORIG(fgetc)(stream);
|
||
_zz_unlock(fd);
|
||
|
||
if(ch == EOF)
|
||
{
|
||
s[i] = '\0';
|
||
if(!i)
|
||
ret = NULL;
|
||
break;
|
||
}
|
||
s[i] = (char)(unsigned char)ch;
|
||
_zz_fuzz(fd, (uint8_t *)s + i, 1); /* rather inefficient */
|
||
_zz_addpos(fd, 1);
|
||
if(s[i] == '\n')
|
||
{
|
||
s[i + 1] = '\0';
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
debug("%s(%p, %i, [%i]) = %p", __func__, s, size, fd, ret);
|
||
return ret;
|
||
}
|
||
|
||
int NEW(ungetc)(int c, FILE *stream)
|
||
{
|
||
int ret, fd;
|
||
|
||
LOADSYM(ungetc);
|
||
fd = fileno(stream);
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
|
||
return ORIG(ungetc)(c, stream);
|
||
|
||
_zz_lock(fd);
|
||
ret = ORIG(ungetc)(c, stream);
|
||
_zz_unlock(fd);
|
||
|
||
if(ret != EOF)
|
||
{
|
||
struct fuzz *fuzz = _zz_getfuzz(fd);
|
||
fuzz->uflag = 1;
|
||
fuzz->upos = _zz_getpos(fd) - 1;
|
||
fuzz->uchar = c;
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
#else
|
||
_zz_addpos(fd, -1);
|
||
#endif
|
||
}
|
||
|
||
if(ret == EOF)
|
||
debug("%s(0x%02x, [%i]) = EOF", __func__, c, fd);
|
||
else
|
||
debug("%s(0x%02x, [%i]) = '%c'", __func__, c, fd, ret);
|
||
|
||
return ret;
|
||
}
|
||
|
||
int NEW(fclose)(FILE *fp)
|
||
{
|
||
int ret, fd;
|
||
|
||
LOADSYM(fclose);
|
||
fd = fileno(fp);
|
||
if(!_zz_ready || !_zz_iswatched(fd))
|
||
return ORIG(fclose)(fp);
|
||
|
||
_zz_lock(fd);
|
||
ret = ORIG(fclose)(fp);
|
||
_zz_unlock(fd);
|
||
debug("%s([%i]) = %i", __func__, fd, ret);
|
||
_zz_unregister(fd);
|
||
|
||
return ret;
|
||
}
|
||
|
||
#define GETDELIM(fn, delim, need_delim) \
|
||
do { \
|
||
char *line; \
|
||
ssize_t done, size; \
|
||
int fd, finished = 0; \
|
||
LOADSYM(fn); \
|
||
LOADSYM(getdelim); \
|
||
LOADSYM(fgetc); \
|
||
fd = fileno(stream); \
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
|
||
return ORIG(getdelim)(lineptr, n, delim, stream); \
|
||
line = *lineptr; \
|
||
size = line ? *n : 0; \
|
||
ret = done = finished = 0; \
|
||
for(;;) \
|
||
{ \
|
||
int ch; \
|
||
if(done >= size) /* highly inefficient but I don't care */ \
|
||
line = realloc(line, size = done + 1); \
|
||
if(finished) \
|
||
{ \
|
||
line[done] = '\0'; \
|
||
*n = size; \
|
||
*lineptr = line; \
|
||
break; \
|
||
} \
|
||
_zz_lock(fd); \
|
||
ch = ORIG(fgetc)(stream); \
|
||
_zz_unlock(fd); \
|
||
if(ch == EOF) \
|
||
{ \
|
||
finished = 1; \
|
||
ret = done; \
|
||
} \
|
||
else \
|
||
{ \
|
||
unsigned char c = ch; \
|
||
_zz_fuzz(fd, &c, 1); /* even more inefficient */ \
|
||
line[done++] = c; \
|
||
_zz_addpos(fd, 1); \
|
||
if(c == delim) \
|
||
{ \
|
||
finished = 1; \
|
||
ret = done; \
|
||
} \
|
||
} \
|
||
} \
|
||
if(need_delim) \
|
||
debug("%s(%p, %p, '%c', [%i]) = %li", __func__, \
|
||
lineptr, n, delim, fd, (long int)ret); \
|
||
else \
|
||
debug("%s(%p, %p, [%i]) = %li", __func__, \
|
||
lineptr, n, fd, (long int)ret); \
|
||
return ret; \
|
||
} while(0)
|
||
|
||
#if defined HAVE_GETLINE
|
||
ssize_t NEW(getline)(char **lineptr, size_t *n, FILE *stream)
|
||
{
|
||
ssize_t ret; GETDELIM(getline, '\n', 0); return ret;
|
||
}
|
||
#endif
|
||
|
||
#if defined HAVE_GETDELIM
|
||
ssize_t NEW(getdelim)(char **lineptr, size_t *n, int delim, FILE *stream)
|
||
{
|
||
ssize_t ret; GETDELIM(getdelim, delim, 1); return ret;
|
||
}
|
||
#endif
|
||
|
||
#if defined HAVE___GETDELIM
|
||
ssize_t NEW(__getdelim)(char **lineptr, size_t *n, int delim, FILE *stream)
|
||
{
|
||
ssize_t ret; GETDELIM(__getdelim, delim, 1); return ret;
|
||
}
|
||
#endif
|
||
|
||
#if defined HAVE_FGETLN
|
||
char *NEW(fgetln)(FILE *stream, size_t *len)
|
||
{
|
||
char *ret;
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
#else
|
||
struct fuzz *fuzz;
|
||
size_t i, size;
|
||
#endif
|
||
int fd;
|
||
|
||
LOADSYM(fgetln);
|
||
LOADSYM(fgetc);
|
||
fd = fileno(stream);
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
|
||
return ORIG(fgetln)(stream, len);
|
||
|
||
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
|
||
_zz_lock(fd);
|
||
ret = ORIG(fgetln)(stream, len);
|
||
_zz_unlock(fd);
|
||
#else
|
||
fuzz = _zz_getfuzz(fd);
|
||
|
||
for(i = size = 0; ; /* i is incremented below */)
|
||
{
|
||
int ch;
|
||
|
||
_zz_lock(fd);
|
||
ch = ORIG(fgetc)(stream);
|
||
_zz_unlock(fd);
|
||
|
||
if(ch == EOF)
|
||
break;
|
||
|
||
if(i >= size)
|
||
fuzz->tmp = realloc(fuzz->tmp, (size += 80));
|
||
|
||
fuzz->tmp[i] = (char)(unsigned char)ch;
|
||
_zz_fuzz(fd, (uint8_t *)fuzz->tmp + i, 1); /* rather inefficient */
|
||
_zz_addpos(fd, 1);
|
||
|
||
if(fuzz->tmp[i++] == '\n')
|
||
break;
|
||
}
|
||
|
||
*len = i;
|
||
ret = fuzz->tmp;
|
||
#endif
|
||
|
||
debug("%s([%i], &%li) = %p", __func__, fd, (long int)*len, ret);
|
||
return ret;
|
||
}
|
||
#endif
|
||
|
||
#if defined HAVE___SREFILL
|
||
int NEW(__srefill)(FILE *fp)
|
||
{
|
||
off_t newpos;
|
||
int ret, fd, tmp;
|
||
|
||
LOADSYM(__srefill);
|
||
fd = fileno(fp);
|
||
if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
|
||
return ORIG(__srefill)(fp);
|
||
|
||
_zz_lock(fd);
|
||
ret = ORIG(__srefill)(fp);
|
||
newpos = lseek(fd, 0, SEEK_CUR);
|
||
_zz_unlock(fd);
|
||
if(ret != EOF)
|
||
{
|
||
if(newpos != -1)
|
||
_zz_setpos(fd, newpos - fp->_r);
|
||
_zz_fuzz(fd, fp->_p, fp->_r);
|
||
_zz_addpos(fd, fp->_r);
|
||
}
|
||
|
||
if(!_zz_islocked(fd))
|
||
debug("%s([%i]) = %i", __func__, fd, ret);
|
||
|
||
return ret;
|
||
}
|
||
#endif
|
||
|