diff --git a/src/myfork.c b/src/myfork.c index 7ee45cc..df126e6 100644 --- a/src/myfork.c +++ b/src/myfork.c @@ -1,591 +1,591 @@ -/* - * zzuf - general purpose fuzzer - * Copyright (c) 2002-2010 Sam Hocevar - * All Rights Reserved - * - * 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. - */ - -/* - * myfork.c: launcher - */ - -#include "config.h" - -#define _INCLUDE_POSIX_SOURCE /* for STDERR_FILENO on HP-UX */ - -#if defined HAVE_STDINT_H -# include -#elif defined HAVE_INTTYPES_H -# include -#endif -#include -#include -#if defined HAVE_UNISTD_H -# include -#endif -#if defined HAVE_WINDOWS_H -# include -# include -# include -#endif -#if defined HAVE_IO_H -# include -#endif -#include -#include /* for O_BINARY */ -#if defined HAVE_SYS_RESOURCE_H -# include /* for RLIMIT_AS */ -#endif - -#include "common.h" -#include "opts.h" -#include "random.h" -#include "fd.h" -#include "fuzz.h" -#include "myfork.h" -#include "md5.h" -#include "timer.h" - -/* Handle old libtool versions */ -#if !defined LT_OBJDIR -# define LT_OBJDIR ".libs/" -#endif - -#if defined RLIMIT_AS -# define ZZUF_RLIMIT_MEM RLIMIT_AS -#elif defined RLIMIT_VMEM -# define ZZUF_RLIMIT_MEM RLIMIT_VMEM -#elif defined RLIMIT_DATA -# define ZZUF_RLIMIT_MEM RLIMIT_DATA -#else -# undef ZZUF_RLIMIT_MEM -#endif - -#if defined RLIMIT_CPU -# define ZZUF_RLIMIT_CPU RLIMIT_CPU -#else -# undef ZZUF_RLIMIT_CPU -#endif - -static int run_process(struct child *child, struct opts *, int[][2]); - -#if defined HAVE_WINDOWS_H -static void rep32(uint8_t *buf, void *addr); -static int dll_inject(PROCESS_INFORMATION *, char const *); -static void *get_proc_address(void *, DWORD, char const *); -#endif - -int myfork(struct child *child, struct opts *opts) -{ - int pipes[3][2]; - pid_t pid; - int i; - - /* Prepare communication pipe */ - for(i = 0; i < 3; i++) - { - int ret; -#if defined HAVE_PIPE - ret = pipe(pipes[i]); -#elif defined HAVE__PIPE && !defined _WIN32 - int tmp; - /* The pipe is created with NOINHERIT otherwise both parts are - * inherited. We then duplicate the part we want. */ - ret = _pipe(pipes[i], 512, _O_BINARY | O_NOINHERIT); - tmp = _dup(pipes[i][1]); - close(pipes[i][1]); - pipes[i][1] = tmp; -#elif defined _WIN32 - // http://www.daniweb.com/software-development/cpp/threads/295780/using-named-pipes-with-asynchronous-io-redirection-to-winapi - { - static int pipe_cnt = 0; - char pipe_name[BUFSIZ]; - HANDLE pipe_hdl[2]; /* [0] read | [1] write */ - HANDLE new_hdl; - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - - ret = 0; - - /* Since we have to use a named pipe, we have to make sure the name is unique */ - _snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\Pipe\\zzuf.%08x.%d", GetCurrentProcessId(), pipe_cnt++); - - /* At this time, the HANDLE is inheritable and can both read/write */ - if ((pipe_hdl[0] = CreateNamedPipeA( - pipe_name, - PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_WAIT, - 1, - BUFSIZ, - BUFSIZ, - 0, - &sa)) == INVALID_HANDLE_VALUE || - - /* Create a new handle for writing access only and it must be inheritable */ - (pipe_hdl[1] = CreateFileA( - pipe_name, - GENERIC_WRITE, - 0, - &sa, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL)) == INVALID_HANDLE_VALUE) - ret = -1; - - /* Now we create a new handle for the listener which is not inheritable */ - if (!DuplicateHandle( - GetCurrentProcess(), pipe_hdl[0], - GetCurrentProcess(), &new_hdl, - 0, FALSE, - DUPLICATE_SAME_ACCESS)) - ret = -1; - - /* Finally we can safetly close the pipe handle */ - CloseHandle(pipe_hdl[0]); - - /* Now we convert handle to fd */ - pipes[i][0] = _open_osfhandle((intptr_t)new_hdl, 0x0); - pipes[i][1] = _open_osfhandle((intptr_t)pipe_hdl[1], 0x0); - } -#endif - if(ret < 0) - { - perror("pipe"); - return -1; - } - } - - pid = run_process(child, opts, pipes); - if(pid < 0) - { - /* FIXME: close pipes */ - fprintf(stderr, "error launching `%s'\n", child->newargv[0]); - return -1; - } - - child->pid = pid; - for(i = 0; i < 3; i++) - { - close(pipes[i][1]); - child->fd[i] = pipes[i][0]; - } - - return 0; -} - -#if !defined HAVE_SETENV -static void setenv(char const *name, char const *value, int overwrite) -{ - char *str; - - if(!overwrite && getenv(name)) - return; - - str = malloc(strlen(name) + 1 + strlen(value) + 1); - sprintf(str, "%s=%s", name, value); - putenv(str); -} -#endif - -static int run_process(struct child *child, struct opts *opts, int pipes[][2]) -{ - char buf[64]; -#if defined HAVE_FORK - static int const files[] = { DEBUG_FILENO, STDERR_FILENO, STDOUT_FILENO }; - char *libpath, *tmp; - int pid, j, len = strlen(opts->oldargv[0]); -# if defined __APPLE__ -# define EXTRAINFO "" -# define PRELOAD "DYLD_INSERT_LIBRARIES" - /* Only enforce flat namespace in preload mode */ - if (opts->opmode == OPMODE_PRELOAD) - setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1); -# elif defined __osf__ -# define EXTRAINFO ":DEFAULT" -# define PRELOAD "_RLD_LIST" -# elif defined __sun && defined __i386 -# define EXTRAINFO "" -# define PRELOAD "LD_PRELOAD_32" -# else -# define EXTRAINFO "" -# define PRELOAD "LD_PRELOAD" -# endif -#elif HAVE_WINDOWS_H - PROCESS_INFORMATION pinfo; - STARTUPINFO sinfo; - HANDLE pid; - char *cmdline; - int i, ret, len; -#endif - -#if defined HAVE_FORK - /* Fork and launch child */ - pid = fork(); - if(pid < 0) - perror("fork"); - if(pid != 0) - return pid; - - /* We loop in reverse order so that files[0] is done last, - * just in case one of the other dup2()ed fds had the value */ - for(j = 3; j--; ) - { - close(pipes[j][0]); - if(pipes[j][1] != files[j]) - { - dup2(pipes[j][1], files[j]); - close(pipes[j][1]); - } - } -#endif - -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM - if(opts->maxmem >= 0) - { - struct rlimit rlim; - rlim.rlim_cur = opts->maxmem * 1048576; - rlim.rlim_max = opts->maxmem * 1048576; - setrlimit(ZZUF_RLIMIT_MEM, &rlim); - } -#endif - -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU - if(opts->maxcpu >= 0) - { - struct rlimit rlim; - rlim.rlim_cur = opts->maxcpu; - rlim.rlim_max = opts->maxcpu + 5; - setrlimit(ZZUF_RLIMIT_CPU, &rlim); - } -#endif - - /* Set environment variables */ -#if defined _WIN32 - sprintf(buf, "%i", _get_osfhandle(pipes[0][1])); -#else - sprintf(buf, "%i", pipes[0][1]); -#endif - setenv("ZZUF_DEBUGFD", buf, 1); - sprintf(buf, "%i", opts->seed); - setenv("ZZUF_SEED", buf, 1); - sprintf(buf, "%g", opts->minratio); - setenv("ZZUF_MINRATIO", buf, 1); - sprintf(buf, "%g", opts->maxratio); - setenv("ZZUF_MAXRATIO", buf, 1); - -#if defined HAVE_FORK - /* Make sure there is space for everything we might do. */ - libpath = malloc(len + strlen(LIBDIR "/" LT_OBJDIR SONAME EXTRAINFO) + 1); - strcpy(libpath, opts->oldargv[0]); - - /* If the binary name contains a '/', we look for a libzzuf in the - * same directory. Otherwise, we only look into the system directory - * to avoid shared library attacks. Write the result in libpath. */ - tmp = strrchr(libpath, '/'); - if(tmp) - { - strcpy(tmp + 1, LT_OBJDIR SONAME); - if(access(libpath, R_OK) < 0) - strcpy(libpath, LIBDIR "/" SONAME); - } - else - strcpy(libpath, LIBDIR "/" SONAME); - - /* OSF1 only */ - strcat(libpath, EXTRAINFO); - - /* Do not clobber previous LD_PRELOAD values */ - tmp = getenv(PRELOAD); - if(tmp && *tmp) - { - char *bigbuf = malloc(strlen(tmp) + strlen(libpath) + 2); - sprintf(bigbuf, "%s:%s", tmp, libpath); - free(libpath); - libpath = bigbuf; - } - - /* Only preload the library in preload mode */ - if (opts->opmode == OPMODE_PRELOAD) - setenv(PRELOAD, libpath, 1); - free(libpath); - - if(execvp(child->newargv[0], child->newargv)) - { - perror(child->newargv[0]); - exit(EXIT_FAILURE); - } - - exit(EXIT_SUCCESS); - /* no return */ - return 0; -#elif HAVE_WINDOWS_H - pid = GetCurrentProcess(); - - /* Inherit standard handles */ - memset(&sinfo, 0, sizeof(sinfo)); - sinfo.cb = sizeof(sinfo); - sinfo.hStdInput = INVALID_HANDLE_VALUE; - sinfo.hStdOutput = (HANDLE)_get_osfhandle(pipes[2][1]); - sinfo.hStdError = (HANDLE)_get_osfhandle(pipes[1][1]); - sinfo.dwFlags = STARTF_USESTDHANDLES; - - /* Build the commandline */ - for (i = 0, len = 0; child->newargv[i]; i++) - len += strlen(child->newargv[i]) + 1; - cmdline = malloc(len); - for (i = 0, len = 0; child->newargv[i]; i++) - { - strcpy(cmdline + len, child->newargv[i]); - len += strlen(child->newargv[i]) + 1; - cmdline[len - 1] = ' '; - } - cmdline[len - 1] = '\0'; - - /* Create the process in suspended state */ - ret = CreateProcess(child->newargv[0], cmdline, NULL, NULL, TRUE, - CREATE_SUSPENDED, NULL, NULL, &sinfo, &pinfo); - free(cmdline); - - child->process_handle = pinfo.hProcess; - child->pid = pinfo.dwProcessId; - - if (!ret) - { - LPTSTR buf; - DWORD err = GetLastError(); - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, 0, (LPTSTR)&buf, 0, NULL); - fprintf(stderr, "error launching `%s': %s\n", child->newargv[0], buf); - LocalFree(buf); - return -1; - } - - /* Insert the replacement code */ - ret = dll_inject(&pinfo, SONAME); - if(ret < 0) - { - TerminateProcess(pinfo.hProcess, -1); - return -1; - } - - /* insert your breakpoint here to have a chance to attach a debugger to libzzuf.dll */ - ret = ResumeThread(pinfo.hThread); - if(ret == -1) - { - TerminateProcess(pinfo.hProcess, -1); - return -1; - } - - return (long int)pinfo.hProcess; -#endif -} - -#if defined HAVE_WINDOWS_H - -static int dll_inject(PROCESS_INFORMATION *pinfo, char const *lib) -{ -#ifdef _M_AMD64 -# define InstructionPointer Rip -# define StackPointer Rsp -# define LoaderRegister Rcx -# define LoadLibraryAOffset 0x15 - - /* This payload allows us to load arbitrary module located at the end of this buffer */ - static uint8_t const ldr[] = - { - "\x55" /* push rbp */ - "\x48\x89\xE5" /* mov rbp,rsp */ - "\x48\x83\xEC\x20" /* sub rsp,byte +0x20 */ - "\x48\x83\xE4\xF0" /* and rsp,byte -0x10 */ - "\x48\x8D\x0D\x14\x00\x00\x00" /* lea rcx,[rel 0x27] */ - "\x48\xB8________" /* mov rax, LoadLibraryA */ - "\xFF\xD0" /* call rax */ - "\x48\x85\xC0" /* test rax,rax */ - "\x75\x01" /* jnz 0x25 */ - "\xCC" /* int3 */ - "\xC9" /* leave */ - "\xC3" /* ret */ - }; - -#elif defined (_M_IX86) -# define InstructionPointer Eip -# define StackPointer Esp -# define LoaderRegister Eax /* It seems the Windows loader store the oep as the first param - * but by a side effect it's also contained in eax register */ -# define ldr ldr32 -# define LoadLibraryAOffset 0x04 - - /* This payload allows us to load arbitrary module located at the end of this buffer */ - static uint8_t const ldr[] = - { - "\x60" /* pushad */ - "\xEB\x0E" /* jmp short 0x11 */ - "\xB8____" /* mov eax,LoadLibraryA */ - "\xFF\xD0" /* call eax */ - "\x85\xC0" /* test eax,eax */ - "\x75\x01" /* jnz 0xf */ - "\xCC" /* int3 */ - "\x61" /* popad */ - "\xC3" /* ret */ - "\xE8\xED\xFF\xFF\xFF" /* call dword 0x3 */ - }; -#else -# error Unimplemented architecture ! -#endif - - int res = -1; - - /* We use this code to make the targeted process waits for us */ - static uint8_t const wait[] = "\xeb\xfe"; /* jmp $-1 */ - size_t wait_len = sizeof(wait) - 1; - uint8_t orig_data[2]; - - void *process = pinfo->hProcess; - void *thread = pinfo->hThread; - DWORD pid = pinfo->dwProcessId; - void *rldlib = NULL; - SIZE_T written = 0; - DWORD old_prot = 0; - - /* Payload */ - void *rpl = NULL; - uint8_t *pl = NULL; - size_t pl_len = sizeof(ldr) - 1 + strlen(lib) + 1; - - CONTEXT ctxt; - DWORD_PTR oep; /* Original Entry Point */ - - /* Use the main thread to inject our library */ - ctxt.ContextFlags = CONTEXT_FULL; - if (!GetThreadContext(thread, &ctxt)) goto _return; - - /* Make the target program waits when it reachs the original entry point, because we can't do many thing from the windows loader */ - oep = ctxt.LoaderRegister; - if (!ReadProcessMemory(process, (LPVOID)oep, orig_data, sizeof(orig_data), &written) || written != sizeof(orig_data)) goto _return; /* save original opcode */ - if (!WriteProcessMemory(process, (LPVOID)oep, wait, wait_len , &written) || written != wait_len) goto _return; /* write jmp short $-1 */ - if (!FlushInstructionCache(process, (LPVOID)oep, wait_len)) goto _return; - if (ResumeThread(thread) == (DWORD)-1) goto _return; - - /* Stop when the program reachs the oep */ - while (oep != ctxt.InstructionPointer) - { - if (!GetThreadContext(thread, &ctxt)) goto _return; - Sleep(10); - } - - if (SuspendThread(thread) == (DWORD)-1) goto _return; - - /* Resolve LoadLibraryA from the target process memory context */ - rldlib = get_proc_address(process, pid, "LoadLibraryA"); - - if ((rpl = VirtualAllocEx(process, NULL, pl_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL) goto _return; - - /* Emulate a call to the ldr code, thus the ret instruction from ldr will get (e|r)ip back to the original entry point */ - ctxt.StackPointer -= sizeof(oep); - if (!WriteProcessMemory(process, (LPVOID)ctxt.StackPointer, &oep, sizeof(oep), &written) || written != sizeof(oep)) goto _return; - ctxt.InstructionPointer = (DWORD_PTR)rpl; - if (!SetThreadContext(thread, &ctxt)) goto _return; - - /* Forge the payload */ - if ((pl = (uint8_t *)malloc(pl_len)) == NULL) goto _return; - memcpy(pl, ldr, sizeof(ldr) - 1); - memcpy(pl + LoadLibraryAOffset, &rldlib, sizeof(rldlib)); /* Write the address of LoadLibraryA */ - strcpy((char *)(pl + sizeof(ldr) - 1), lib); /* Write the first parameter of LoadLibraryA */ - - if (!WriteProcessMemory(process, rpl, pl, pl_len, &written) || written != pl_len) goto _return; - - /* Restore original opcode */ - if (!WriteProcessMemory(process, (LPVOID)oep, orig_data, sizeof(orig_data), &written) || written != sizeof(orig_data)) goto _return; - - if (!FlushInstructionCache(process, rpl, pl_len)) goto _return; - if (!FlushInstructionCache(process, (LPVOID)oep, sizeof(orig_data))) goto _return; - - res = 0; -_return: - if (pl != NULL) free(pl); - - /* We must not free remote allocated memory since they will be used after the process will be resumed */ - return res; - -#undef InstructionPointer -#undef StackPointer -#undef LoaderRegister -#undef LoadLibraryAOffset -} - -static void *get_proc_address(void *process, DWORD pid, const char *func) -{ - char buf[1024]; - size_t buflen = strlen(func) + 1; - - MODULEENTRY32 entry; - void *ret = 0; - SIZE_T tmp; - void *list; - int i, k; - - list = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); - entry.dwSize = sizeof(entry); - for(k = Module32First(list, &entry); k; k = Module32Next(list, &entry)) - { - IMAGE_DOS_HEADER dos; - IMAGE_NT_HEADERS nt; - IMAGE_EXPORT_DIRECTORY expdir; - - uint32_t exportaddr; - uint8_t *base = entry.modBaseAddr; - - if (strcmp("kernel32.dll", entry.szModule)) - continue; - - ReadProcessMemory(process, base, &dos, sizeof(dos), &tmp); - ReadProcessMemory(process, base + dos.e_lfanew, &nt, sizeof(nt), &tmp); - - exportaddr = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; - if (!exportaddr) - continue; - - ReadProcessMemory(process, base + exportaddr, &expdir, sizeof(expdir), &tmp); - - for (i = 0; i < (int)expdir.NumberOfNames; i++) - { - uint32_t nameaddr, funcaddr; - uint16_t j; - - /* Look for our function name in the list of names */ - ReadProcessMemory(process, base + expdir.AddressOfNames - + i * sizeof(DWORD), - &nameaddr, sizeof(nameaddr), &tmp); - ReadProcessMemory(process, base + nameaddr, buf, buflen, &tmp); - - if (strcmp(buf, func)) - continue; - - /* If we found a function with this name, return its address */ - ReadProcessMemory(process, base + expdir.AddressOfNameOrdinals - + i * sizeof(WORD), - &j, sizeof(j), &tmp); - ReadProcessMemory(process, base + expdir.AddressOfFunctions - + j * sizeof(DWORD), - &funcaddr, sizeof(funcaddr), &tmp); - - ret = base + funcaddr; - goto _finished; - } - } - -_finished: - CloseHandle(list); - return ret; -} - -#endif +/* + * zzuf - general purpose fuzzer + * Copyright (c) 2002-2010 Sam Hocevar + * All Rights Reserved + * + * 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. + */ + +/* + * myfork.c: launcher + */ + +#include "config.h" + +#define _INCLUDE_POSIX_SOURCE /* for STDERR_FILENO on HP-UX */ + +#if defined HAVE_STDINT_H +# include +#elif defined HAVE_INTTYPES_H +# include +#endif +#include +#include +#if defined HAVE_UNISTD_H +# include +#endif +#if defined HAVE_WINDOWS_H +# include +# include +# include +#endif +#if defined HAVE_IO_H +# include +#endif +#include +#include /* for O_BINARY */ +#if defined HAVE_SYS_RESOURCE_H +# include /* for RLIMIT_AS */ +#endif + +#include "common.h" +#include "opts.h" +#include "random.h" +#include "fd.h" +#include "fuzz.h" +#include "myfork.h" +#include "md5.h" +#include "timer.h" + +/* Handle old libtool versions */ +#if !defined LT_OBJDIR +# define LT_OBJDIR ".libs/" +#endif + +#if defined RLIMIT_AS +# define ZZUF_RLIMIT_MEM RLIMIT_AS +#elif defined RLIMIT_VMEM +# define ZZUF_RLIMIT_MEM RLIMIT_VMEM +#elif defined RLIMIT_DATA +# define ZZUF_RLIMIT_MEM RLIMIT_DATA +#else +# undef ZZUF_RLIMIT_MEM +#endif + +#if defined RLIMIT_CPU +# define ZZUF_RLIMIT_CPU RLIMIT_CPU +#else +# undef ZZUF_RLIMIT_CPU +#endif + +static int run_process(struct child *child, struct opts *, int[][2]); + +#if defined HAVE_WINDOWS_H +static void rep32(uint8_t *buf, void *addr); +static int dll_inject(PROCESS_INFORMATION *, char const *); +static void *get_proc_address(void *, DWORD, char const *); +#endif + +int myfork(struct child *child, struct opts *opts) +{ + int pipes[3][2]; + pid_t pid; + int i; + + /* Prepare communication pipe */ + for(i = 0; i < 3; i++) + { + int ret; +#if defined HAVE_PIPE + ret = pipe(pipes[i]); +#elif defined HAVE__PIPE && !defined _WIN32 + int tmp; + /* The pipe is created with NOINHERIT otherwise both parts are + * inherited. We then duplicate the part we want. */ + ret = _pipe(pipes[i], 512, _O_BINARY | O_NOINHERIT); + tmp = _dup(pipes[i][1]); + close(pipes[i][1]); + pipes[i][1] = tmp; +#elif defined _WIN32 + // http://www.daniweb.com/software-development/cpp/threads/295780/using-named-pipes-with-asynchronous-io-redirection-to-winapi + { + static int pipe_cnt = 0; + char pipe_name[BUFSIZ]; + HANDLE pipe_hdl[2]; /* [0] read | [1] write */ + HANDLE new_hdl; + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + ret = 0; + + /* Since we have to use a named pipe, we have to make sure the name is unique */ + _snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\Pipe\\zzuf.%08x.%d", GetCurrentProcessId(), pipe_cnt++); + + /* At this time, the HANDLE is inheritable and can both read/write */ + if ((pipe_hdl[0] = CreateNamedPipeA( + pipe_name, + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_WAIT, + 1, + BUFSIZ, + BUFSIZ, + 0, + &sa)) == INVALID_HANDLE_VALUE || + + /* Create a new handle for writing access only and it must be inheritable */ + (pipe_hdl[1] = CreateFileA( + pipe_name, + GENERIC_WRITE, + 0, + &sa, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL)) == INVALID_HANDLE_VALUE) + ret = -1; + + /* Now we create a new handle for the listener which is not inheritable */ + if (!DuplicateHandle( + GetCurrentProcess(), pipe_hdl[0], + GetCurrentProcess(), &new_hdl, + 0, FALSE, + DUPLICATE_SAME_ACCESS)) + ret = -1; + + /* Finally we can safetly close the pipe handle */ + CloseHandle(pipe_hdl[0]); + + /* Now we convert handle to fd */ + pipes[i][0] = _open_osfhandle((intptr_t)new_hdl, 0x0); + pipes[i][1] = _open_osfhandle((intptr_t)pipe_hdl[1], 0x0); + } +#endif + if(ret < 0) + { + perror("pipe"); + return -1; + } + } + + pid = run_process(child, opts, pipes); + if(pid < 0) + { + /* FIXME: close pipes */ + fprintf(stderr, "error launching `%s'\n", child->newargv[0]); + return -1; + } + + child->pid = pid; + for(i = 0; i < 3; i++) + { + close(pipes[i][1]); + child->fd[i] = pipes[i][0]; + } + + return 0; +} + +#if !defined HAVE_SETENV +static void setenv(char const *name, char const *value, int overwrite) +{ + char *str; + + if(!overwrite && getenv(name)) + return; + + str = malloc(strlen(name) + 1 + strlen(value) + 1); + sprintf(str, "%s=%s", name, value); + putenv(str); +} +#endif + +static int run_process(struct child *child, struct opts *opts, int pipes[][2]) +{ + char buf[64]; +#if defined HAVE_FORK + static int const files[] = { DEBUG_FILENO, STDERR_FILENO, STDOUT_FILENO }; + char *libpath, *tmp; + int pid, j, len = strlen(opts->oldargv[0]); +# if defined __APPLE__ +# define EXTRAINFO "" +# define PRELOAD "DYLD_INSERT_LIBRARIES" + /* Only enforce flat namespace in preload mode */ + if (opts->opmode == OPMODE_PRELOAD) + setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1); +# elif defined __osf__ +# define EXTRAINFO ":DEFAULT" +# define PRELOAD "_RLD_LIST" +# elif defined __sun && defined __i386 +# define EXTRAINFO "" +# define PRELOAD "LD_PRELOAD_32" +# else +# define EXTRAINFO "" +# define PRELOAD "LD_PRELOAD" +# endif +#elif HAVE_WINDOWS_H + PROCESS_INFORMATION pinfo; + STARTUPINFO sinfo; + HANDLE pid; + char *cmdline; + int i, ret, len; +#endif + +#if defined HAVE_FORK + /* Fork and launch child */ + pid = fork(); + if(pid < 0) + perror("fork"); + if(pid != 0) + return pid; + + /* We loop in reverse order so that files[0] is done last, + * just in case one of the other dup2()ed fds had the value */ + for(j = 3; j--; ) + { + close(pipes[j][0]); + if(pipes[j][1] != files[j]) + { + dup2(pipes[j][1], files[j]); + close(pipes[j][1]); + } + } +#endif + +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM + if(opts->maxmem >= 0) + { + struct rlimit rlim; + rlim.rlim_cur = opts->maxmem * 1048576; + rlim.rlim_max = opts->maxmem * 1048576; + setrlimit(ZZUF_RLIMIT_MEM, &rlim); + } +#endif + +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU + if(opts->maxcpu >= 0) + { + struct rlimit rlim; + rlim.rlim_cur = opts->maxcpu; + rlim.rlim_max = opts->maxcpu + 5; + setrlimit(ZZUF_RLIMIT_CPU, &rlim); + } +#endif + + /* Set environment variables */ +#if defined _WIN32 + sprintf(buf, "%i", _get_osfhandle(pipes[0][1])); +#else + sprintf(buf, "%i", pipes[0][1]); +#endif + setenv("ZZUF_DEBUGFD", buf, 1); + sprintf(buf, "%i", opts->seed); + setenv("ZZUF_SEED", buf, 1); + sprintf(buf, "%g", opts->minratio); + setenv("ZZUF_MINRATIO", buf, 1); + sprintf(buf, "%g", opts->maxratio); + setenv("ZZUF_MAXRATIO", buf, 1); + +#if defined HAVE_FORK + /* Make sure there is space for everything we might do. */ + libpath = malloc(len + strlen(LIBDIR "/" LT_OBJDIR SONAME EXTRAINFO) + 1); + strcpy(libpath, opts->oldargv[0]); + + /* If the binary name contains a '/', we look for a libzzuf in the + * same directory. Otherwise, we only look into the system directory + * to avoid shared library attacks. Write the result in libpath. */ + tmp = strrchr(libpath, '/'); + if(tmp) + { + strcpy(tmp + 1, LT_OBJDIR SONAME); + if(access(libpath, R_OK) < 0) + strcpy(libpath, LIBDIR "/" SONAME); + } + else + strcpy(libpath, LIBDIR "/" SONAME); + + /* OSF1 only */ + strcat(libpath, EXTRAINFO); + + /* Do not clobber previous LD_PRELOAD values */ + tmp = getenv(PRELOAD); + if(tmp && *tmp) + { + char *bigbuf = malloc(strlen(tmp) + strlen(libpath) + 2); + sprintf(bigbuf, "%s:%s", tmp, libpath); + free(libpath); + libpath = bigbuf; + } + + /* Only preload the library in preload mode */ + if (opts->opmode == OPMODE_PRELOAD) + setenv(PRELOAD, libpath, 1); + free(libpath); + + if(execvp(child->newargv[0], child->newargv)) + { + perror(child->newargv[0]); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + /* no return */ + return 0; +#elif HAVE_WINDOWS_H + pid = GetCurrentProcess(); + + /* Inherit standard handles */ + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.cb = sizeof(sinfo); + sinfo.hStdInput = INVALID_HANDLE_VALUE; + sinfo.hStdOutput = (HANDLE)_get_osfhandle(pipes[2][1]); + sinfo.hStdError = (HANDLE)_get_osfhandle(pipes[1][1]); + sinfo.dwFlags = STARTF_USESTDHANDLES; + + /* Build the commandline */ + for (i = 0, len = 0; child->newargv[i]; i++) + len += strlen(child->newargv[i]) + 1; + cmdline = malloc(len); + for (i = 0, len = 0; child->newargv[i]; i++) + { + strcpy(cmdline + len, child->newargv[i]); + len += strlen(child->newargv[i]) + 1; + cmdline[len - 1] = ' '; + } + cmdline[len - 1] = '\0'; + + /* Create the process in suspended state */ + ret = CreateProcess(child->newargv[0], cmdline, NULL, NULL, TRUE, + CREATE_SUSPENDED, NULL, NULL, &sinfo, &pinfo); + free(cmdline); + + child->process_handle = pinfo.hProcess; + child->pid = pinfo.dwProcessId; + + if (!ret) + { + LPTSTR buf; + DWORD err = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, 0, (LPTSTR)&buf, 0, NULL); + fprintf(stderr, "error launching `%s': %s\n", child->newargv[0], buf); + LocalFree(buf); + return -1; + } + + /* Insert the replacement code */ + ret = dll_inject(&pinfo, SONAME); + if(ret < 0) + { + TerminateProcess(pinfo.hProcess, -1); + return -1; + } + + /* insert your breakpoint here to have a chance to attach a debugger to libzzuf.dll */ + ret = ResumeThread(pinfo.hThread); + if(ret == -1) + { + TerminateProcess(pinfo.hProcess, -1); + return -1; + } + + return (long int)pinfo.hProcess; +#endif +} + +#if defined HAVE_WINDOWS_H + +static int dll_inject(PROCESS_INFORMATION *pinfo, char const *lib) +{ +#ifdef _M_AMD64 +# define InstructionPointer Rip +# define StackPointer Rsp +# define LoaderRegister Rcx +# define LoadLibraryAOffset 0x15 + + /* This payload allows us to load arbitrary module located at the end of this buffer */ + static uint8_t const ldr[] = + { + "\x55" /* push rbp */ + "\x48\x89\xE5" /* mov rbp,rsp */ + "\x48\x83\xEC\x20" /* sub rsp,byte +0x20 */ + "\x48\x83\xE4\xF0" /* and rsp,byte -0x10 */ + "\x48\x8D\x0D\x14\x00\x00\x00" /* lea rcx,[rel 0x27] */ + "\x48\xB8________" /* mov rax, LoadLibraryA */ + "\xFF\xD0" /* call rax */ + "\x48\x85\xC0" /* test rax,rax */ + "\x75\x01" /* jnz 0x25 */ + "\xCC" /* int3 */ + "\xC9" /* leave */ + "\xC3" /* ret */ + }; + +#elif defined (_M_IX86) +# define InstructionPointer Eip +# define StackPointer Esp +# define LoaderRegister Eax /* It seems the Windows loader store the oep as the first param + * but by a side effect it's also contained in eax register */ +# define ldr ldr32 +# define LoadLibraryAOffset 0x04 + + /* This payload allows us to load arbitrary module located at the end of this buffer */ + static uint8_t const ldr[] = + { + "\x60" /* pushad */ + "\xEB\x0E" /* jmp short 0x11 */ + "\xB8____" /* mov eax,LoadLibraryA */ + "\xFF\xD0" /* call eax */ + "\x85\xC0" /* test eax,eax */ + "\x75\x01" /* jnz 0xf */ + "\xCC" /* int3 */ + "\x61" /* popad */ + "\xC3" /* ret */ + "\xE8\xED\xFF\xFF\xFF" /* call dword 0x3 */ + }; +#else +# error Unimplemented architecture ! +#endif + + int res = -1; + + /* We use this code to make the targeted process waits for us */ + static uint8_t const wait[] = "\xeb\xfe"; /* jmp $-1 */ + size_t wait_len = sizeof(wait) - 1; + uint8_t orig_data[2]; + + void *process = pinfo->hProcess; + void *thread = pinfo->hThread; + DWORD pid = pinfo->dwProcessId; + void *rldlib = NULL; + SIZE_T written = 0; + DWORD old_prot = 0; + + /* Payload */ + void *rpl = NULL; + uint8_t *pl = NULL; + size_t pl_len = sizeof(ldr) - 1 + strlen(lib) + 1; + + CONTEXT ctxt; + DWORD_PTR oep; /* Original Entry Point */ + + /* Use the main thread to inject our library */ + ctxt.ContextFlags = CONTEXT_FULL; + if (!GetThreadContext(thread, &ctxt)) goto _return; + + /* Make the target program waits when it reachs the original entry point, because we can't do many thing from the windows loader */ + oep = ctxt.LoaderRegister; + if (!ReadProcessMemory(process, (LPVOID)oep, orig_data, sizeof(orig_data), &written) || written != sizeof(orig_data)) goto _return; /* save original opcode */ + if (!WriteProcessMemory(process, (LPVOID)oep, wait, wait_len , &written) || written != wait_len) goto _return; /* write jmp short $-1 */ + if (!FlushInstructionCache(process, (LPVOID)oep, wait_len)) goto _return; + if (ResumeThread(thread) == (DWORD)-1) goto _return; + + /* Stop when the program reachs the oep */ + while (oep != ctxt.InstructionPointer) + { + if (!GetThreadContext(thread, &ctxt)) goto _return; + Sleep(10); + } + + if (SuspendThread(thread) == (DWORD)-1) goto _return; + + /* Resolve LoadLibraryA from the target process memory context */ + rldlib = get_proc_address(process, pid, "LoadLibraryA"); + + if ((rpl = VirtualAllocEx(process, NULL, pl_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL) goto _return; + + /* Emulate a call to the ldr code, thus the ret instruction from ldr will get (e|r)ip back to the original entry point */ + ctxt.StackPointer -= sizeof(oep); + if (!WriteProcessMemory(process, (LPVOID)ctxt.StackPointer, &oep, sizeof(oep), &written) || written != sizeof(oep)) goto _return; + ctxt.InstructionPointer = (DWORD_PTR)rpl; + if (!SetThreadContext(thread, &ctxt)) goto _return; + + /* Forge the payload */ + if ((pl = (uint8_t *)malloc(pl_len)) == NULL) goto _return; + memcpy(pl, ldr, sizeof(ldr) - 1); + memcpy(pl + LoadLibraryAOffset, &rldlib, sizeof(rldlib)); /* Write the address of LoadLibraryA */ + strcpy((char *)(pl + sizeof(ldr) - 1), lib); /* Write the first parameter of LoadLibraryA */ + + if (!WriteProcessMemory(process, rpl, pl, pl_len, &written) || written != pl_len) goto _return; + + /* Restore original opcode */ + if (!WriteProcessMemory(process, (LPVOID)oep, orig_data, sizeof(orig_data), &written) || written != sizeof(orig_data)) goto _return; + + if (!FlushInstructionCache(process, rpl, pl_len)) goto _return; + if (!FlushInstructionCache(process, (LPVOID)oep, sizeof(orig_data))) goto _return; + + res = 0; +_return: + if (pl != NULL) free(pl); + + /* We must not free remote allocated memory since they will be used after the process will be resumed */ + return res; + +#undef InstructionPointer +#undef StackPointer +#undef LoaderRegister +#undef LoadLibraryAOffset +} + +static void *get_proc_address(void *process, DWORD pid, const char *func) +{ + char buf[1024]; + size_t buflen = strlen(func) + 1; + + MODULEENTRY32 entry; + void *ret = 0; + SIZE_T tmp; + void *list; + int i, k; + + list = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); + entry.dwSize = sizeof(entry); + for(k = Module32First(list, &entry); k; k = Module32Next(list, &entry)) + { + IMAGE_DOS_HEADER dos; + IMAGE_NT_HEADERS nt; + IMAGE_EXPORT_DIRECTORY expdir; + + uint32_t exportaddr; + uint8_t *base = entry.modBaseAddr; + + if (strcmp("kernel32.dll", entry.szModule)) + continue; + + ReadProcessMemory(process, base, &dos, sizeof(dos), &tmp); + ReadProcessMemory(process, base + dos.e_lfanew, &nt, sizeof(nt), &tmp); + + exportaddr = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (!exportaddr) + continue; + + ReadProcessMemory(process, base + exportaddr, &expdir, sizeof(expdir), &tmp); + + for (i = 0; i < (int)expdir.NumberOfNames; i++) + { + uint32_t nameaddr, funcaddr; + uint16_t j; + + /* Look for our function name in the list of names */ + ReadProcessMemory(process, base + expdir.AddressOfNames + + i * sizeof(DWORD), + &nameaddr, sizeof(nameaddr), &tmp); + ReadProcessMemory(process, base + nameaddr, buf, buflen, &tmp); + + if (strcmp(buf, func)) + continue; + + /* If we found a function with this name, return its address */ + ReadProcessMemory(process, base + expdir.AddressOfNameOrdinals + + i * sizeof(WORD), + &j, sizeof(j), &tmp); + ReadProcessMemory(process, base + expdir.AddressOfFunctions + + j * sizeof(DWORD), + &funcaddr, sizeof(funcaddr), &tmp); + + ret = base + funcaddr; + goto _finished; + } + } + +_finished: + CloseHandle(list); + return ret; +} + +#endif diff --git a/src/zzuf.c b/src/zzuf.c index c297d07..44da8ad 100644 --- a/src/zzuf.c +++ b/src/zzuf.c @@ -1,1277 +1,1277 @@ -/* - * zzuf - general purpose fuzzer - * Copyright (c) 2002-2010 Sam Hocevar - * All Rights Reserved - * - * 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. - */ - -/* - * main.c: main program - */ - -#include "config.h" - -#define _INCLUDE_POSIX_SOURCE /* for STDERR_FILENO on HP-UX */ - -#if defined HAVE_STDINT_H -# include -#elif defined HAVE_INTTYPES_H -# include -#endif -#include -#include -#if defined HAVE_UNISTD_H -# include /* for read(), write(), close() */ -#endif -#if defined HAVE_REGEX_H -# if _WIN32 -# include "com_regexp.hpp" -# else -# include -# endif -#endif -#if defined HAVE_WINSOCK2_H -# include /* for fd_set */ -#endif -#if defined HAVE_IO_H -# include -#endif -#include -#include -#include -#if defined HAVE_SYS_TIME_H -# include -#endif -#if defined HAVE_SYS_WAIT_H -# include -#endif -#if defined HAVE_SYS_RESOURCE_H -# include /* for RLIMIT_AS */ -#endif - -#include - -#include "common.h" -#include "opts.h" -#include "random.h" -#include "fd.h" -#include "fuzz.h" -#include "myfork.h" -#include "md5.h" -#include "timer.h" - -#if !defined SIGKILL -# define SIGKILL 9 -#endif - -#if defined RLIMIT_AS -# define ZZUF_RLIMIT_MEM RLIMIT_AS -#elif defined RLIMIT_VMEM -# define ZZUF_RLIMIT_MEM RLIMIT_VMEM -#elif defined RLIMIT_DATA -# define ZZUF_RLIMIT_MEM RLIMIT_DATA -#else -# undef ZZUF_RLIMIT_MEM -#endif - -#if defined RLIMIT_CPU -# define ZZUF_RLIMIT_CPU RLIMIT_CPU -#else -# undef ZZUF_RLIMIT_CPU -#endif - -static void loop_stdin(struct opts *); - -static void spawn_children(struct opts *); -static void clean_children(struct opts *); -static void read_children(struct opts *); - -#if !defined HAVE_SETENV -static void setenv(char const *, char const *, int); -#endif -#if defined HAVE_WAITPID -static char const *sig2name(int); -#endif -static void finfo(FILE *, struct opts *, uint32_t); -#if defined HAVE_REGEX_H -static char *merge_regex(char *, char *); -static char *merge_file(char *, char *); -#endif -static void version(void); -static void usage(void); - -#define ZZUF_FD_SET(fd, p_fdset, maxfd) \ - if(fd >= 0) \ - { \ - FD_SET((unsigned int)fd, p_fdset); \ - if(fd > maxfd) \ - maxfd = fd; \ - } - -#define ZZUF_FD_ISSET(fd, p_fdset) \ - ((fd >= 0) && (FD_ISSET(fd, p_fdset))) - -#if defined _WIN32 -# include -# include /* _O_RDWR */ -# include /* _open */ -static CRITICAL_SECTION _zz_pipe_cs; -#endif - -int main(int argc, char *argv[]) -{ - struct opts _opts, *opts = &_opts; - char *tmp; -#if defined HAVE_REGEX_H - char *include = NULL, *exclude = NULL; - int cmdline = 0; -#endif - int debug = 0, network = 0; - int i; - -#if defined _WIN32 - InitializeCriticalSection(&_zz_pipe_cs); -#endif - - _zz_opts_init(opts); - - for(;;) - { -#if defined HAVE_REGEX_H -# define OPTSTR_REGEX "cE:I:" -#else -# define OPTSTR_REGEX "" -#endif -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM -# define OPTSTR_RLIMIT_MEM "M:" -#else -# define OPTSTR_RLIMIT_MEM "" -#endif -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU -# define OPTSTR_RLIMIT_CPU "T:" -#else -# define OPTSTR_RLIMIT_CPU "" -#endif -#define OPTSTR "+" OPTSTR_REGEX OPTSTR_RLIMIT_MEM OPTSTR_RLIMIT_CPU \ - "a:Ab:B:C:dD:e:f:F:ij:l:mnO:p:P:qr:R:s:St:U:vxhV" -#define MOREINFO "Try `%s --help' for more information.\n" - int option_index = 0; - static struct caca_option long_options[] = - { - /* Long option, needs arg, flag, short option */ - { "allow", 1, NULL, 'a' }, - { "autoinc", 0, NULL, 'A' }, - { "bytes", 1, NULL, 'b' }, - { "max-bytes", 1, NULL, 'B' }, -#if defined HAVE_REGEX_H - { "cmdline", 0, NULL, 'c' }, -#endif - { "max-crashes", 1, NULL, 'C' }, - { "debug", 0, NULL, 'd' }, - { "delay", 1, NULL, 'D' }, -#if defined HAVE_REGEX_H - { "exclude", 1, NULL, 'E' }, -#endif - { "fuzzing", 1, NULL, 'f' }, - { "stdin", 0, NULL, 'i' }, -#if defined HAVE_REGEX_H - { "include", 1, NULL, 'I' }, -#endif - { "jobs", 1, NULL, 'j' }, - { "list", 1, NULL, 'l' }, - { "md5", 0, NULL, 'm' }, - { "max-memory", 1, NULL, 'M' }, - { "network", 0, NULL, 'n' }, - { "opmode", 1, NULL, 'O' }, - { "ports", 1, NULL, 'p' }, - { "protect", 1, NULL, 'P' }, - { "quiet", 0, NULL, 'q' }, - { "ratio", 1, NULL, 'r' }, - { "refuse", 1, NULL, 'R' }, - { "seed", 1, NULL, 's' }, - { "signal", 0, NULL, 'S' }, - { "max-time", 1, NULL, 't' }, - { "max-cputime", 1, NULL, 'T' }, - { "max-usertime", 1, NULL, 'U' }, - { "verbose", 0, NULL, 'v' }, - { "check-exit", 0, NULL, 'x' }, - { "help", 0, NULL, 'h' }, - { "version", 0, NULL, 'V' }, - { NULL, 0, NULL, 0 } - }; - int c = caca_getopt(argc, argv, OPTSTR, long_options, &option_index); - - if(c == -1) - break; - - switch(c) - { - case 'a': /* --allow */ - opts->allow = caca_optarg; - break; - case 'A': /* --autoinc */ - setenv("ZZUF_AUTOINC", "1", 1); - break; - case 'b': /* --bytes */ - opts->bytes = caca_optarg; - break; - case 'B': /* --max-bytes */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->maxbytes = atoi(caca_optarg); - break; -#if defined HAVE_REGEX_H - case 'c': /* --cmdline */ - cmdline = 1; - break; -#endif - case 'C': /* --max-crashes */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->maxcrashes = atoi(caca_optarg); - if(opts->maxcrashes <= 0) - opts->maxcrashes = 0; - break; - case 'd': /* --debug */ - debug++; - break; - case 'D': /* --delay */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->delay = (int64_t)(atof(caca_optarg) * 1000000.0); - break; -#if defined HAVE_REGEX_H - case 'E': /* --exclude */ - exclude = merge_regex(exclude, caca_optarg); - if(!exclude) - { - fprintf(stderr, "%s: invalid regex -- `%s'\n", - argv[0], caca_optarg); - _zz_opts_fini(opts); - return EXIT_FAILURE; - } - break; -#endif - case 'f': /* --fuzzing */ - opts->fuzzing = caca_optarg; - break; - case 'F': - fprintf(stderr, "%s: `-F' is deprecated, use `-j'\n", argv[0]); - _zz_opts_fini(opts); - return EXIT_FAILURE; - case 'i': /* --stdin */ - setenv("ZZUF_STDIN", "1", 1); - break; -#if defined HAVE_REGEX_H - case 'I': /* --include */ - include = merge_regex(include, caca_optarg); - if(!include) - { - fprintf(stderr, "%s: invalid regex -- `%s'\n", - argv[0], caca_optarg); - _zz_opts_fini(opts); - return EXIT_FAILURE; - } - break; -#endif - case 'j': /* --jobs */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->maxchild = atoi(caca_optarg) > 1 ? atoi(caca_optarg) : 1; - break; - case 'l': /* --list */ - opts->list = caca_optarg; - break; - case 'm': /* --md5 */ - opts->md5 = 1; - break; -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM - case 'M': /* --max-memory */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->maxmem = atoi(caca_optarg); - break; -#endif - case 'n': /* --network */ - setenv("ZZUF_NETWORK", "1", 1); - network = 1; - break; - case 'O': /* --opmode */ - if(caca_optarg[0] == '=') - caca_optarg++; - if (!strcmp(caca_optarg, "preload")) - opts->opmode = OPMODE_PRELOAD; - else if (!strcmp(caca_optarg, "copy")) - opts->opmode = OPMODE_COPY; - else - { - fprintf(stderr, "%s: invalid operating mode -- `%s'\n", - argv[0], caca_optarg); - _zz_opts_fini(opts); - return EXIT_FAILURE; - } - break; - case 'p': /* --ports */ - opts->ports = caca_optarg; - break; - case 'P': /* --protect */ - opts->protect = caca_optarg; - break; - case 'q': /* --quiet */ - opts->quiet = 1; - break; - case 'r': /* --ratio */ - if(caca_optarg[0] == '=') - caca_optarg++; - tmp = strchr(caca_optarg, ':'); - opts->minratio = atof(caca_optarg); - opts->maxratio = tmp ? atof(tmp + 1) : opts->minratio; - break; - case 'R': /* --refuse */ - opts->refuse = caca_optarg; - break; - case 's': /* --seed */ - if(caca_optarg[0] == '=') - caca_optarg++; - tmp = strchr(caca_optarg, ':'); - opts->seed = atol(caca_optarg); - opts->endseed = tmp ? tmp[1] ? (uint32_t)atol(tmp + 1) - : (uint32_t)-1L - : opts->seed + 1; - break; - case 'S': /* --signal */ - setenv("ZZUF_SIGNAL", "1", 1); - break; - case 't': /* --max-time */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->maxtime = (int64_t)atoi(caca_optarg) * 1000000; - break; -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU - case 'T': /* --max-cputime */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->maxcpu = (int)(atof(caca_optarg) + 0.5); - break; -#endif - case 'U': /* --max-usertime */ - if(caca_optarg[0] == '=') - caca_optarg++; - opts->maxusertime = (int64_t)(atof(caca_optarg) * 1000000.0); - break; - case 'x': /* --check-exit */ - opts->checkexit = 1; - break; - case 'v': /* --verbose */ - opts->verbose = 1; - break; - case 'h': /* --help */ - usage(); - _zz_opts_fini(opts); - return 0; - case 'V': /* --version */ - version(); - _zz_opts_fini(opts); - return 0; - default: - fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c); - printf(MOREINFO, argv[0]); - _zz_opts_fini(opts); - return EXIT_FAILURE; - } - } - - if(opts->ports && !network) - { - fprintf(stderr, "%s: port option (-p) requires network fuzzing (-n)\n", - argv[0]); - printf(MOREINFO, argv[0]); - _zz_opts_fini(opts); - return EXIT_FAILURE; - } - - if (opts->allow && !network) - { - fprintf(stderr, "%s: allow option (-a) requires network fuzzing (-n)\n", - argv[0]); - printf(MOREINFO, argv[0]); - _zz_opts_fini(opts); - return EXIT_FAILURE; - } - - _zz_setratio(opts->minratio, opts->maxratio); - _zz_setseed(opts->seed); - - if(opts->fuzzing) - _zz_fuzzing(opts->fuzzing); - if(opts->bytes) - _zz_bytes(opts->bytes); - if(opts->list) - _zz_list(opts->list); - if(opts->protect) - _zz_protect(opts->protect); - if(opts->refuse) - _zz_refuse(opts->refuse); - - /* Needed for stdin mode and for copy opmode. */ - _zz_fd_init(); - - /* - * Mode 1: asked to read from the standard input - */ - if(caca_optind >= argc) - { - if(opts->verbose) - { - finfo(stderr, opts, opts->seed); - fprintf(stderr, "reading from stdin\n"); - } - - if(opts->endseed != opts->seed + 1) - { - fprintf(stderr, "%s: seed ranges are incompatible with " - "stdin fuzzing\n", argv[0]); - printf(MOREINFO, argv[0]); - _zz_opts_fini(opts); - return EXIT_FAILURE; - } - - loop_stdin(opts); - } - /* - * Mode 2: asked to launch programs - */ - else - { -#if defined HAVE_REGEX_H - if(cmdline) - { - int dashdash = 0; - - for(i = caca_optind + 1; i < argc; i++) - { - if(dashdash) - include = merge_file(include, argv[i]); - else if(!strcmp("--", argv[i])) - dashdash = 1; - else if(argv[i][0] != '-') - include = merge_file(include, argv[i]); - } - } - - if(include) - setenv("ZZUF_INCLUDE", include, 1); - if(exclude) - setenv("ZZUF_EXCLUDE", exclude, 1); -#endif - - setenv("ZZUF_DEBUG", debug ? debug > 1 ? "2" : "1" : "0", 1); - - if(opts->fuzzing) - setenv("ZZUF_FUZZING", opts->fuzzing, 1); - if(opts->bytes) - setenv("ZZUF_BYTES", opts->bytes, 1); - if(opts->list) - setenv("ZZUF_LIST", opts->list, 1); - if(opts->ports) - setenv("ZZUF_PORTS", opts->ports, 1); - if(opts->allow && opts->allow[0] == '!') - setenv("ZZUF_DENY", opts->allow, 1); - else if(opts->allow) - setenv("ZZUF_ALLOW", opts->allow, 1); - if(opts->protect) - setenv("ZZUF_PROTECT", opts->protect, 1); - if(opts->refuse) - setenv("ZZUF_REFUSE", opts->refuse, 1); -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM - if(opts->maxmem >= 0) - { - char buf[32]; - snprintf(buf, 32, "%i", opts->maxmem); - setenv("ZZUF_MEMORY", buf, 1); - } -#endif - - /* Allocate memory for children handling */ - opts->child = malloc(opts->maxchild * sizeof(struct child)); - for(i = 0; i < opts->maxchild; i++) - { - opts->child[i].status = STATUS_FREE; - memset(opts->child[i].fd, -1, sizeof(opts->child->fd)); - } - opts->nchild = 0; - - /* Create new argv */ - opts->oldargc = argc; - opts->oldargv = argv; - for(i = 0; i < opts->maxchild; i++) - { - int len = argc - caca_optind; - opts->child[i].newargv = malloc((len + 1) * sizeof(char *)); - memcpy(opts->child[i].newargv, argv + caca_optind, - len * sizeof(char *)); - opts->child[i].newargv[len] = (char *)NULL; - } - - /* Main loop */ - while(opts->nchild || opts->seed < opts->endseed) - { - /* Spawn new children, if necessary */ - spawn_children(opts); - - /* Cleanup dead or dying children */ - clean_children(opts); - - /* Read data from children */ - read_children(opts); - - if(opts->maxcrashes && opts->crashes >= opts->maxcrashes - && opts->nchild == 0) - { - if(opts->verbose) - fprintf(stderr, - "zzuf: maximum crash count reached, exiting\n"); - break; - } - - if(opts->maxtime && _zz_time() - opts->starttime >= opts->maxtime - && opts->nchild == 0) - { - if(opts->verbose) - fprintf(stderr, - "zzuf: maximum running time reached, exiting\n"); - break; - } - } - } - - /* Clean up */ - _zz_fd_fini(); - _zz_opts_fini(opts); - -#if defined _WIN32 - DeleteCriticalSection(&_zz_pipe_cs); -#endif - - return opts->crashes ? EXIT_FAILURE : EXIT_SUCCESS; -} - -static void loop_stdin(struct opts *opts) -{ - uint8_t md5sum[16]; - struct md5 *ctx = NULL; - int total = 0; - - if(opts->md5) - ctx = _zz_md5_init(); - - _zz_register(0); - - for(;;) - { - uint8_t buf[BUFSIZ]; - int ret, toread = BUFSIZ, off = 0, nw = 0; - - if(opts->maxbytes >= 0) - { - if(total >= opts->maxbytes) - break; - if(total + BUFSIZ >= opts->maxbytes) - toread = opts->maxbytes - total; - } - - ret = read(0, buf, toread); - if(ret <= 0) - break; - - total += ret; - - _zz_fuzz(0, buf, ret); - _zz_addpos(0, ret); - - if(opts->md5) - _zz_md5_add(ctx, buf, ret); - else while(ret) - { - if((nw = write(1, buf + off, (unsigned int)ret)) < 0) - break; - ret -= nw; - off += nw; - } - } - - if(opts->md5) - { - _zz_md5_fini(md5sum, ctx); - finfo(stdout, opts, opts->seed); - fprintf(stdout, "%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x" - "%.02x%.02x%.02x%.02x%.02x%.02x\n", md5sum[0], md5sum[1], - md5sum[2], md5sum[3], md5sum[4], md5sum[5], md5sum[6], - md5sum[7], md5sum[8], md5sum[9], md5sum[10], md5sum[11], - md5sum[12], md5sum[13], md5sum[14], md5sum[15]); - fflush(stdout); - } - - _zz_unregister(0); -} - -static void finfo(FILE *fp, struct opts *opts, uint32_t seed) -{ - if(opts->minratio == opts->maxratio) - fprintf(fp, "zzuf[s=%i,r=%g]: ", seed, opts->minratio); - else - fprintf(fp, "zzuf[s=%i,r=%g:%g]: ", seed, - opts->minratio, opts->maxratio); -} - -#if defined HAVE_REGEX_H -static char *merge_file(char *regex, char *file) -{ - char *newfile = malloc(5 + 2 * strlen(file) + 1 + 1), *tmp = newfile; - - *tmp++ = '('; - *tmp++ = '^'; - *tmp++ = '|'; - *tmp++ = '/'; - *tmp++ = ')'; - while(*file) - { - if(strchr("^.[$()|*+?{\\", *file)) - *tmp++ = '\\'; - *tmp++ = *file++; - } - *tmp++ = '$'; - *tmp++ = '\0'; - - tmp = merge_regex(regex, newfile); - free(newfile); - return tmp; -} - -static char *merge_regex(char *regex, char *string) -{ - regex_t optre; - - if(regex) - { - regex = realloc(regex, strlen(regex) + strlen(string) + 1 + 1); - sprintf(regex + strlen(regex) - 1, "|%s)", string); - } - else - { - regex = malloc(1 + strlen(string) + 1 + 1); - sprintf(regex, "(%s)", string); - } - - if(regcomp(&optre, regex, REG_EXTENDED) != 0) - { - free(regex); - return NULL; - } - regfree(&optre); - - return regex; -} -#endif - -static void spawn_children(struct opts *opts) -{ - int64_t now = _zz_time(); - int i; - - if(opts->nchild == opts->maxchild) - return; /* no slot */ - - if(opts->seed == opts->endseed) - return; /* job finished */ - - if(opts->maxcrashes && opts->crashes >= opts->maxcrashes) - return; /* all jobs crashed */ - - if(opts->maxtime && now - opts->starttime >= opts->maxtime) - return; /* run time exceeded */ - - if(opts->delay > 0 && opts->lastlaunch + opts->delay > now) - return; /* too early */ - - /* Find the empty slot */ - for(i = 0; i < opts->maxchild; i++) - if(opts->child[i].status == STATUS_FREE) - break; - - /* Prepare required files, if necessary */ - if (opts->opmode == OPMODE_COPY) - { - char tmpname[4096]; - char *tmpdir; - FILE *fpin; - int j, k = 0, fdout; - - tmpdir = getenv("TEMP"); - if (!tmpdir || !*tmpdir) - tmpdir = "/tmp"; - - for (j = caca_optind + 1; j < opts->oldargc; j++) - { - fpin = fopen(opts->oldargv[j], "r"); - if (!fpin) - continue; - -#ifdef _WIN32 -# - sprintf(tmpname, "%s/zzuf.$i.XXXXXX", tmpdir, GetCurrentProcessId()); - fdout = _open(mktemp(tmpname), _O_RDWR, 0600); -#else - sprintf(tmpname, "%s/zzuf.%i.XXXXXX", tmpdir, (int)getpid()); - fdout = mkstemp(tmpname); -#endif - if (fdout < 0) - { - fclose(fpin); - continue; - } - - opts->child[i].newargv[j - caca_optind] = strdup(tmpname); - - _zz_register(k); - while(!feof(fpin)) - { - uint8_t buf[BUFSIZ]; - size_t n = fread(buf, 1, BUFSIZ, fpin); - if (n <= 0) - break; - _zz_fuzz(k, buf, n); - _zz_addpos(k, n); - write(fdout, buf, n); - } - _zz_unregister(k); - - fclose(fpin); - close(fdout); - - k++; - } - } - - /* Launch process */ - if (myfork(&opts->child[i], opts) < 0) - { - fprintf(stderr, "error launching `%s'\n", opts->child[i].newargv[0]); - opts->seed++; - /* FIXME: clean up OPMODE_COPY files here */ - return; - } - - /* We’re the parent, acknowledge spawn */ - opts->child[i].date = now; - opts->child[i].bytes = 0; - opts->child[i].seed = opts->seed; - opts->child[i].ratio = _zz_getratio(); - opts->child[i].status = STATUS_RUNNING; - if(opts->md5) - opts->child[i].ctx = _zz_md5_init(); - - if(opts->verbose) - { - finfo(stderr, opts, opts->child[i].seed); - fprintf(stderr, "launched `%s'\n", opts->child[i].newargv[0]); - } - - opts->lastlaunch = now; - opts->nchild++; - opts->seed++; - - _zz_setseed(opts->seed); -} - -static void clean_children(struct opts *opts) -{ -#if defined HAVE_KILL || defined HAVE_WINDOWS_H - int64_t now = _zz_time(); -#endif - int i, j; - -#if defined HAVE_KILL || defined HAVE_WINDOWS_H - /* Terminate children if necessary */ - for(i = 0; i < opts->maxchild; i++) - { - if(opts->child[i].status == STATUS_RUNNING - && opts->maxbytes >= 0 - && opts->child[i].bytes > opts->maxbytes) - { - if(opts->verbose) - { - finfo(stderr, opts, opts->child[i].seed); - fprintf(stderr, "data output exceeded, sending SIGTERM\n"); - } -#if defined HAVE_KILL - kill(opts->child[i].pid, SIGTERM); -#else - /* We must invalidate fd */ - memset(opts->child[i].fd, -1, sizeof(opts->child[i].fd)); - TerminateProcess(opts->child[i].process_handle, 0x0); -#endif - opts->child[i].date = now; - opts->child[i].status = STATUS_SIGTERM; - } - - if(opts->child[i].status == STATUS_RUNNING - && opts->maxusertime >= 0 - && now > opts->child[i].date + opts->maxusertime) - { - if(opts->verbose) - { - finfo(stderr, opts, opts->child[i].seed); - fprintf(stderr, "running time exceeded, sending SIGTERM\n"); - } -#if defined HAVE_KILL - kill(opts->child[i].pid, SIGTERM); -#else - /* We must invalidate fd */ - memset(opts->child[i].fd, -1, sizeof(opts->child[i].fd)); - TerminateProcess(opts->child[i].process_handle, 0x0); -#endif - opts->child[i].date = now; - opts->child[i].status = STATUS_SIGTERM; - } - } - - /* Kill children if necessary (still there after 2 seconds) */ - for(i = 0; i < opts->maxchild; i++) - { - if(opts->child[i].status == STATUS_SIGTERM - && now > opts->child[i].date + 2000000) - { - if(opts->verbose) - { - finfo(stderr, opts, opts->child[i].seed); - fprintf(stderr, "not responding, sending SIGKILL\n"); - } -#if defined HAVE_KILL - kill(opts->child[i].pid, SIGKILL); -#else - TerminateProcess(opts->child[i].process_handle, 0x0); -#endif - opts->child[i].status = STATUS_SIGKILL; - } - } -#endif - - /* Collect dead children */ - for(i = 0; i < opts->maxchild; i++) - { - uint8_t md5sum[16]; -#if defined HAVE_WAITPID - int status; - pid_t pid; -#endif - - if(opts->child[i].status != STATUS_SIGKILL - && opts->child[i].status != STATUS_SIGTERM - && opts->child[i].status != STATUS_EOF) - continue; - -#if defined HAVE_WAITPID - pid = waitpid(opts->child[i].pid, &status, WNOHANG); - if(pid <= 0) - continue; - - if(opts->checkexit && WIFEXITED(status) && WEXITSTATUS(status)) - { - finfo(stderr, opts, opts->child[i].seed); - fprintf(stderr, "exit %i\n", WEXITSTATUS(status)); - opts->crashes++; - } - else if(WIFSIGNALED(status) - && !(WTERMSIG(status) == SIGTERM - && opts->child[i].status == STATUS_SIGTERM)) - { - char const *message = ""; - - if(WTERMSIG(status) == SIGKILL && opts->maxmem >= 0) - message = " (memory exceeded?)"; -# if defined SIGXCPU - else if(WTERMSIG(status) == SIGXCPU && opts->maxcpu >= 0) - message = " (CPU time exceeded?)"; -# endif - else if(WTERMSIG(status) == SIGKILL && opts->maxcpu >= 0) - message = " (CPU time exceeded?)"; - - finfo(stderr, opts, opts->child[i].seed); - fprintf(stderr, "signal %i%s%s\n", - WTERMSIG(status), sig2name(WTERMSIG(status)), message); - opts->crashes++; - } - else if (opts->verbose) - { - finfo(stderr, opts, opts->child[i].seed); - if (WIFSIGNALED(status)) - fprintf(stderr, "signal %i%s\n", - WTERMSIG(status), sig2name(WTERMSIG(status))); - else - fprintf(stderr, "exit %i\n", WEXITSTATUS(status)); - } -#elif defined _WIN32 - { - DWORD exit_code; - if (GetExitCodeProcess(opts->child[i].process_handle, &exit_code)) - { - if (exit_code == STILL_ACTIVE) continue; /* The process is still active, we don't do anything */ - - /* - * The main problem with GetExitCodeProcess is it returns either returned parameter value of - * ExitProcess/TerminateProcess, or the unhandled exception (which is what we're looking for) - */ - switch (exit_code) - { - case EXCEPTION_ACCESS_VIOLATION: fprintf(stderr, "child(%d) unhandled exception: Access Violation\n", opts->child[i].pid); break; - default: fprintf(stderr, "child(%d) exited with code %#08x\n", opts->child[i].pid, exit_code); break; - } - } - - if (opts->child[i].status != STATUS_RUNNING) - { - TerminateProcess(opts->child[i].process_handle, 0); - } - } -#else - /* waitpid() is not available. Don't kill the process. */ - continue; -#endif - - for(j = 0; j < 3; j++) - if(opts->child[i].fd[j] >= 0) - close(opts->child[i].fd[j]); - - if (opts->opmode == OPMODE_COPY) - { - for (j = caca_optind + 1; j < opts->oldargc; j++) - { - if (opts->child[i].newargv[j - caca_optind] != opts->oldargv[j]) - { - unlink(opts->child[i].newargv[j - caca_optind]); - free(opts->child[i].newargv[j - caca_optind]); - opts->child[i].newargv[j - caca_optind] = opts->oldargv[j]; - } - } - } - - if(opts->md5) - { - _zz_md5_fini(md5sum, opts->child[i].ctx); - finfo(stdout, opts, opts->child[i].seed); - fprintf(stdout, "%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x" - "%.02x%.02x%.02x%.02x%.02x%.02x%.02x\n", md5sum[0], - md5sum[1], md5sum[2], md5sum[3], md5sum[4], md5sum[5], - md5sum[6], md5sum[7], md5sum[8], md5sum[9], md5sum[10], - md5sum[11], md5sum[12], md5sum[13], md5sum[14], md5sum[15]); - fflush(stdout); - } - opts->child[i].status = STATUS_FREE; - opts->nchild--; - } -} - -#ifdef _WIN32 - -/* This structure contains useful information about data sent from fuzzed applications */ -struct child_overlapped -{ - OVERLAPPED overlapped; - char buf[BUFSIZ]; - struct opts * opts; - int child_no; - int fd_no; -}; - -/* This callback is called when fuzzed applications write in fd out, err or debug */ -static void _stdcall read_child(DWORD err_code, DWORD nbr_of_bytes_transfered, LPOVERLAPPED overlapped) -{ - struct child_overlapped * co = (struct child_overlapped *)overlapped; - - /* TODO: handle more cases like ERROR_MORE_DATA */ - if (err_code != ERROR_SUCCESS) return; - - EnterCriticalSection(&_zz_pipe_cs); - switch (co->fd_no) - { - case 0: /* debug fd */ - write(1, "dbg: ", 4); - case 1: /* out */ - write(1, co->buf, nbr_of_bytes_transfered); break; - case 2: /* err */ - write(2, co->buf, nbr_of_bytes_transfered); break; - default: break; - } - LeaveCriticalSection(&_zz_pipe_cs); - - if(co->fd_no != 0) /* either out or err fd */ - co->opts->child[co->child_no].bytes += nbr_of_bytes_transfered; - - if(co->opts->md5 && co->fd_no == 2) - _zz_md5_add(co->opts->child[co->child_no].ctx, co->buf, nbr_of_bytes_transfered); - - free(co); /* clean up allocated data */ -} - -/* Since on windows select doesn't support file HANDLE, we use IOCP */ -static void read_children(struct opts *opts) -{ - size_t i, j; - HANDLE *children_handle, * cur_child_handle; - size_t fd_number = opts->maxchild * 3; - - cur_child_handle = children_handle = malloc(sizeof(*children_handle) * fd_number); - - for(i = 0; i < fd_number; i++) - children_handle[i] = INVALID_HANDLE_VALUE; - - /* XXX: cute (i, j) iterating hack */ - for(i = 0, j = 0; i < (size_t)opts->maxchild; i += (j == 2), j = (j + 1) % 3) - { - struct child_overlapped * co; - HANDLE h = (opts->child[i].fd[j] == -1) ? INVALID_HANDLE_VALUE : (HANDLE)_get_osfhandle(opts->child[i].fd[j]); - - if(opts->child[i].status != STATUS_RUNNING - || opts->child[i].fd[j] == -1 - || h == INVALID_HANDLE_VALUE) - { - fd_number--; - continue; - } - - co = malloc(sizeof(*co)); - ZeroMemory(co, sizeof(*co)); - *cur_child_handle = co->overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - co->child_no = i; - co->fd_no = j; - co->opts = opts; - - if(!ReadFileEx(h, co->buf, sizeof(co->buf), (LPOVERLAPPED)co, read_child)) - { - /* End of file reached */ - close(opts->child[i].fd[j]); - opts->child[i].fd[j] = -1; - - if(opts->child[i].fd[0] == -1 - && opts->child[i].fd[1] == -1 - && opts->child[i].fd[2] == -1) - opts->child[i].status = STATUS_EOF; - } - cur_child_handle++; - } - - if (fd_number == 0) return; - - /* FIXME: handle error */ - WaitForMultipleObjectsEx(fd_number, children_handle, FALSE, 1000, TRUE); -} - -#else -static void read_children(struct opts *opts) -{ - struct timeval tv; - fd_set fdset; - int i, j, ret, maxfd = 0; - - /* Read data from all sockets */ - FD_ZERO(&fdset); - for(i = 0; i < opts->maxchild; i++) - { - if(opts->child[i].status != STATUS_RUNNING) - continue; - - for(j = 0; j < 3; j++) - ZZUF_FD_SET(opts->child[i].fd[j], &fdset, maxfd); - } - tv.tv_sec = 0; - tv.tv_usec = 1000; - - errno = 0; - ret = select(maxfd + 1, &fdset, NULL, NULL, &tv); - if(ret < 0 && errno) - perror("select"); - if(ret <= 0) - return; - - /* XXX: cute (i, j) iterating hack */ - for(i = 0, j = 0; i < opts->maxchild; i += (j == 2), j = (j + 1) % 3) - { - uint8_t buf[BUFSIZ]; - - if(opts->child[i].status != STATUS_RUNNING) - continue; - - if(!ZZUF_FD_ISSET(opts->child[i].fd[j], &fdset)) - continue; - - ret = read(opts->child[i].fd[j], buf, BUFSIZ - 1); - if(ret > 0) - { - /* We got data */ - if(j != 0) - opts->child[i].bytes += ret; - - if(opts->md5 && j == 2) - _zz_md5_add(opts->child[i].ctx, buf, ret); - else if(!opts->quiet || j == 0) - write((j < 2) ? STDERR_FILENO : STDOUT_FILENO, buf, ret); - } - else if(ret == 0) - { - /* End of file reached */ - close(opts->child[i].fd[j]); - opts->child[i].fd[j] = -1; - - if(opts->child[i].fd[0] == -1 - && opts->child[i].fd[1] == -1 - && opts->child[i].fd[2] == -1) - opts->child[i].status = STATUS_EOF; - } - } -} -#endif - -#if !defined HAVE_SETENV -static void setenv(char const *name, char const *value, int overwrite) -{ - char *str; - - if(!overwrite && getenv(name)) - return; - - str = malloc(strlen(name) + 1 + strlen(value) + 1); - sprintf(str, "%s=%s", name, value); - putenv(str); -} -#endif - -#if defined HAVE_WAITPID -static char const *sig2name(int signum) -{ - switch(signum) - { -#ifdef SIGQUIT - case SIGQUIT: return " (SIGQUIT)"; /* 3 */ -#endif - case SIGILL: return " (SIGILL)"; /* 4 */ -#ifdef SIGTRAP - case SIGTRAP: return " (SIGTRAP)"; /* 5 */ -#endif - case SIGABRT: return " (SIGABRT)"; /* 6 */ -#ifdef SIGBUS - case SIGBUS: return " (SIGBUS)"; /* 7 */ -#endif - case SIGFPE: return " (SIGFPE)"; /* 8 */ - case SIGSEGV: return " (SIGSEGV)"; /* 11 */ - case SIGPIPE: return " (SIGPIPE)"; /* 13 */ -#ifdef SIGEMT - case SIGEMT: return " (SIGEMT)"; /* ? */ -#endif -#ifdef SIGXCPU - case SIGXCPU: return " (SIGXCPU)"; /* 24 */ -#endif -#ifdef SIGXFSZ - case SIGXFSZ: return " (SIGXFSZ)"; /* 25 */ -#endif -#ifdef SIGSYS - case SIGSYS: return " (SIGSYS)"; /* 31 */ -#endif - } - - return ""; -} -#endif - -static void version(void) -{ - printf("zzuf %s\n", PACKAGE_VERSION); - printf("Copyright (C) 2002-2010 Sam Hocevar \n"); - printf("This program is free software. It comes without any warranty, to the extent\n"); - printf("permitted by applicable law. You can redistribute it and/or modify it under\n"); - printf("the terms of the Do What The Fuck You Want To Public License, Version 2, as\n"); - printf("published by Sam Hocevar. See for more details.\n"); - printf("\n"); - printf("Written by Sam Hocevar. Report bugs to .\n"); -} - -static void usage(void) -{ -#if defined HAVE_REGEX_H - printf("Usage: zzuf [-aAcdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n"); -#else - printf("Usage: zzuf [-aAdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n"); -#endif - printf(" [-f mode] [-D delay] [-j jobs] [-C crashes] [-B bytes] [-a list]\n"); - printf(" [-t seconds]"); -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU - printf( " [-T seconds]"); -#endif -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM - printf( " [-M mebibytes]"); -#endif - printf( " [-b ranges] [-p ports]\n"); - printf(" [-P protect] [-R refuse] [-l list]"); -#if defined HAVE_REGEX_H - printf( " [-I include] [-E exclude]"); -#endif - printf(" [-O mode]\n"); - printf("\n"); - printf(" [PROGRAM [--] [ARGS]...]\n"); - printf(" zzuf -h | --help\n"); - printf(" zzuf -V | --version\n"); - printf("Run PROGRAM with optional arguments ARGS and fuzz its input.\n"); - printf("\n"); - printf("Mandatory arguments to long options are mandatory for short options too.\n"); - printf(" -a, --allow only fuzz network input for IPs in \n"); - printf(" ... ! do not fuzz network input for IPs in \n"); - printf(" -A, --autoinc increment seed each time a new file is opened\n"); - printf(" -b, --bytes only fuzz bytes at offsets within \n"); - printf(" -B, --max-bytes kill children that output more than bytes\n"); -#if defined HAVE_REGEX_H - printf(" -c, --cmdline only fuzz files specified in the command line\n"); -#endif - printf(" -C, --max-crashes stop after children have crashed (default 1)\n"); - printf(" -d, --debug print debug messages (twice for more verbosity)\n"); - printf(" -D, --delay delay between forks\n"); -#if defined HAVE_REGEX_H - printf(" -E, --exclude do not fuzz files matching \n"); -#endif - printf(" -f, --fuzzing use fuzzing mode ([xor] set unset)\n"); - printf(" -i, --stdin fuzz standard input\n"); -#if defined HAVE_REGEX_H - printf(" -I, --include only fuzz files matching \n"); -#endif - printf(" -j, --jobs number of simultaneous jobs (default 1)\n"); - printf(" -l, --list only fuzz Nth descriptor with N in \n"); - printf(" -m, --md5 compute the output's MD5 hash\n"); -#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM - printf(" -M, --max-memory maximum child virtual memory in MiB (default %u)\n", DEFAULT_MEM); -#endif - printf(" -n, --network fuzz network input\n"); - printf(" -O, --opmode use operating mode ([preload] copy)\n"); - printf(" -p, --ports only fuzz network destination ports in \n"); - printf(" -P, --protect protect bytes and characters in \n"); - printf(" -q, --quiet do not print children's messages\n"); - printf(" -r, --ratio bit fuzzing ratio (default %g)\n", DEFAULT_RATIO); - printf(" ... specify a ratio range\n"); - printf(" -R, --refuse refuse bytes and characters in \n"); - printf(" -s, --seed random seed (default %i)\n", DEFAULT_SEED); - printf(" ... specify a seed range\n"); - printf(" -S, --signal prevent children from diverting crashing signals\n"); - printf(" -t, --max-time stop spawning children after seconds\n"); - printf(" -T, --max-cputime kill children that use more than CPU seconds\n"); - printf(" -U, --max-usertime kill children that run for more than seconds\n"); - printf(" -v, --verbose print information during the run\n"); - printf(" -x, --check-exit report processes that exit with a non-zero status\n"); - printf(" -h, --help display this help and exit\n"); - printf(" -V, --version output version information and exit\n"); - printf("\n"); - printf("Written by Sam Hocevar. Report bugs to .\n"); -} - +/* + * zzuf - general purpose fuzzer + * Copyright (c) 2002-2010 Sam Hocevar + * All Rights Reserved + * + * 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. + */ + +/* + * main.c: main program + */ + +#include "config.h" + +#define _INCLUDE_POSIX_SOURCE /* for STDERR_FILENO on HP-UX */ + +#if defined HAVE_STDINT_H +# include +#elif defined HAVE_INTTYPES_H +# include +#endif +#include +#include +#if defined HAVE_UNISTD_H +# include /* for read(), write(), close() */ +#endif +#if defined HAVE_REGEX_H +# if _WIN32 +# include "com_regexp.hpp" +# else +# include +# endif +#endif +#if defined HAVE_WINSOCK2_H +# include /* for fd_set */ +#endif +#if defined HAVE_IO_H +# include +#endif +#include +#include +#include +#if defined HAVE_SYS_TIME_H +# include +#endif +#if defined HAVE_SYS_WAIT_H +# include +#endif +#if defined HAVE_SYS_RESOURCE_H +# include /* for RLIMIT_AS */ +#endif + +#include + +#include "common.h" +#include "opts.h" +#include "random.h" +#include "fd.h" +#include "fuzz.h" +#include "myfork.h" +#include "md5.h" +#include "timer.h" + +#if !defined SIGKILL +# define SIGKILL 9 +#endif + +#if defined RLIMIT_AS +# define ZZUF_RLIMIT_MEM RLIMIT_AS +#elif defined RLIMIT_VMEM +# define ZZUF_RLIMIT_MEM RLIMIT_VMEM +#elif defined RLIMIT_DATA +# define ZZUF_RLIMIT_MEM RLIMIT_DATA +#else +# undef ZZUF_RLIMIT_MEM +#endif + +#if defined RLIMIT_CPU +# define ZZUF_RLIMIT_CPU RLIMIT_CPU +#else +# undef ZZUF_RLIMIT_CPU +#endif + +static void loop_stdin(struct opts *); + +static void spawn_children(struct opts *); +static void clean_children(struct opts *); +static void read_children(struct opts *); + +#if !defined HAVE_SETENV +static void setenv(char const *, char const *, int); +#endif +#if defined HAVE_WAITPID +static char const *sig2name(int); +#endif +static void finfo(FILE *, struct opts *, uint32_t); +#if defined HAVE_REGEX_H +static char *merge_regex(char *, char *); +static char *merge_file(char *, char *); +#endif +static void version(void); +static void usage(void); + +#define ZZUF_FD_SET(fd, p_fdset, maxfd) \ + if(fd >= 0) \ + { \ + FD_SET((unsigned int)fd, p_fdset); \ + if(fd > maxfd) \ + maxfd = fd; \ + } + +#define ZZUF_FD_ISSET(fd, p_fdset) \ + ((fd >= 0) && (FD_ISSET(fd, p_fdset))) + +#if defined _WIN32 +# include +# include /* _O_RDWR */ +# include /* _open */ +static CRITICAL_SECTION _zz_pipe_cs; +#endif + +int main(int argc, char *argv[]) +{ + struct opts _opts, *opts = &_opts; + char *tmp; +#if defined HAVE_REGEX_H + char *include = NULL, *exclude = NULL; + int cmdline = 0; +#endif + int debug = 0, network = 0; + int i; + +#if defined _WIN32 + InitializeCriticalSection(&_zz_pipe_cs); +#endif + + _zz_opts_init(opts); + + for(;;) + { +#if defined HAVE_REGEX_H +# define OPTSTR_REGEX "cE:I:" +#else +# define OPTSTR_REGEX "" +#endif +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM +# define OPTSTR_RLIMIT_MEM "M:" +#else +# define OPTSTR_RLIMIT_MEM "" +#endif +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU +# define OPTSTR_RLIMIT_CPU "T:" +#else +# define OPTSTR_RLIMIT_CPU "" +#endif +#define OPTSTR "+" OPTSTR_REGEX OPTSTR_RLIMIT_MEM OPTSTR_RLIMIT_CPU \ + "a:Ab:B:C:dD:e:f:F:ij:l:mnO:p:P:qr:R:s:St:U:vxhV" +#define MOREINFO "Try `%s --help' for more information.\n" + int option_index = 0; + static struct caca_option long_options[] = + { + /* Long option, needs arg, flag, short option */ + { "allow", 1, NULL, 'a' }, + { "autoinc", 0, NULL, 'A' }, + { "bytes", 1, NULL, 'b' }, + { "max-bytes", 1, NULL, 'B' }, +#if defined HAVE_REGEX_H + { "cmdline", 0, NULL, 'c' }, +#endif + { "max-crashes", 1, NULL, 'C' }, + { "debug", 0, NULL, 'd' }, + { "delay", 1, NULL, 'D' }, +#if defined HAVE_REGEX_H + { "exclude", 1, NULL, 'E' }, +#endif + { "fuzzing", 1, NULL, 'f' }, + { "stdin", 0, NULL, 'i' }, +#if defined HAVE_REGEX_H + { "include", 1, NULL, 'I' }, +#endif + { "jobs", 1, NULL, 'j' }, + { "list", 1, NULL, 'l' }, + { "md5", 0, NULL, 'm' }, + { "max-memory", 1, NULL, 'M' }, + { "network", 0, NULL, 'n' }, + { "opmode", 1, NULL, 'O' }, + { "ports", 1, NULL, 'p' }, + { "protect", 1, NULL, 'P' }, + { "quiet", 0, NULL, 'q' }, + { "ratio", 1, NULL, 'r' }, + { "refuse", 1, NULL, 'R' }, + { "seed", 1, NULL, 's' }, + { "signal", 0, NULL, 'S' }, + { "max-time", 1, NULL, 't' }, + { "max-cputime", 1, NULL, 'T' }, + { "max-usertime", 1, NULL, 'U' }, + { "verbose", 0, NULL, 'v' }, + { "check-exit", 0, NULL, 'x' }, + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + int c = caca_getopt(argc, argv, OPTSTR, long_options, &option_index); + + if(c == -1) + break; + + switch(c) + { + case 'a': /* --allow */ + opts->allow = caca_optarg; + break; + case 'A': /* --autoinc */ + setenv("ZZUF_AUTOINC", "1", 1); + break; + case 'b': /* --bytes */ + opts->bytes = caca_optarg; + break; + case 'B': /* --max-bytes */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->maxbytes = atoi(caca_optarg); + break; +#if defined HAVE_REGEX_H + case 'c': /* --cmdline */ + cmdline = 1; + break; +#endif + case 'C': /* --max-crashes */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->maxcrashes = atoi(caca_optarg); + if(opts->maxcrashes <= 0) + opts->maxcrashes = 0; + break; + case 'd': /* --debug */ + debug++; + break; + case 'D': /* --delay */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->delay = (int64_t)(atof(caca_optarg) * 1000000.0); + break; +#if defined HAVE_REGEX_H + case 'E': /* --exclude */ + exclude = merge_regex(exclude, caca_optarg); + if(!exclude) + { + fprintf(stderr, "%s: invalid regex -- `%s'\n", + argv[0], caca_optarg); + _zz_opts_fini(opts); + return EXIT_FAILURE; + } + break; +#endif + case 'f': /* --fuzzing */ + opts->fuzzing = caca_optarg; + break; + case 'F': + fprintf(stderr, "%s: `-F' is deprecated, use `-j'\n", argv[0]); + _zz_opts_fini(opts); + return EXIT_FAILURE; + case 'i': /* --stdin */ + setenv("ZZUF_STDIN", "1", 1); + break; +#if defined HAVE_REGEX_H + case 'I': /* --include */ + include = merge_regex(include, caca_optarg); + if(!include) + { + fprintf(stderr, "%s: invalid regex -- `%s'\n", + argv[0], caca_optarg); + _zz_opts_fini(opts); + return EXIT_FAILURE; + } + break; +#endif + case 'j': /* --jobs */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->maxchild = atoi(caca_optarg) > 1 ? atoi(caca_optarg) : 1; + break; + case 'l': /* --list */ + opts->list = caca_optarg; + break; + case 'm': /* --md5 */ + opts->md5 = 1; + break; +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM + case 'M': /* --max-memory */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->maxmem = atoi(caca_optarg); + break; +#endif + case 'n': /* --network */ + setenv("ZZUF_NETWORK", "1", 1); + network = 1; + break; + case 'O': /* --opmode */ + if(caca_optarg[0] == '=') + caca_optarg++; + if (!strcmp(caca_optarg, "preload")) + opts->opmode = OPMODE_PRELOAD; + else if (!strcmp(caca_optarg, "copy")) + opts->opmode = OPMODE_COPY; + else + { + fprintf(stderr, "%s: invalid operating mode -- `%s'\n", + argv[0], caca_optarg); + _zz_opts_fini(opts); + return EXIT_FAILURE; + } + break; + case 'p': /* --ports */ + opts->ports = caca_optarg; + break; + case 'P': /* --protect */ + opts->protect = caca_optarg; + break; + case 'q': /* --quiet */ + opts->quiet = 1; + break; + case 'r': /* --ratio */ + if(caca_optarg[0] == '=') + caca_optarg++; + tmp = strchr(caca_optarg, ':'); + opts->minratio = atof(caca_optarg); + opts->maxratio = tmp ? atof(tmp + 1) : opts->minratio; + break; + case 'R': /* --refuse */ + opts->refuse = caca_optarg; + break; + case 's': /* --seed */ + if(caca_optarg[0] == '=') + caca_optarg++; + tmp = strchr(caca_optarg, ':'); + opts->seed = atol(caca_optarg); + opts->endseed = tmp ? tmp[1] ? (uint32_t)atol(tmp + 1) + : (uint32_t)-1L + : opts->seed + 1; + break; + case 'S': /* --signal */ + setenv("ZZUF_SIGNAL", "1", 1); + break; + case 't': /* --max-time */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->maxtime = (int64_t)atoi(caca_optarg) * 1000000; + break; +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU + case 'T': /* --max-cputime */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->maxcpu = (int)(atof(caca_optarg) + 0.5); + break; +#endif + case 'U': /* --max-usertime */ + if(caca_optarg[0] == '=') + caca_optarg++; + opts->maxusertime = (int64_t)(atof(caca_optarg) * 1000000.0); + break; + case 'x': /* --check-exit */ + opts->checkexit = 1; + break; + case 'v': /* --verbose */ + opts->verbose = 1; + break; + case 'h': /* --help */ + usage(); + _zz_opts_fini(opts); + return 0; + case 'V': /* --version */ + version(); + _zz_opts_fini(opts); + return 0; + default: + fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c); + printf(MOREINFO, argv[0]); + _zz_opts_fini(opts); + return EXIT_FAILURE; + } + } + + if(opts->ports && !network) + { + fprintf(stderr, "%s: port option (-p) requires network fuzzing (-n)\n", + argv[0]); + printf(MOREINFO, argv[0]); + _zz_opts_fini(opts); + return EXIT_FAILURE; + } + + if (opts->allow && !network) + { + fprintf(stderr, "%s: allow option (-a) requires network fuzzing (-n)\n", + argv[0]); + printf(MOREINFO, argv[0]); + _zz_opts_fini(opts); + return EXIT_FAILURE; + } + + _zz_setratio(opts->minratio, opts->maxratio); + _zz_setseed(opts->seed); + + if(opts->fuzzing) + _zz_fuzzing(opts->fuzzing); + if(opts->bytes) + _zz_bytes(opts->bytes); + if(opts->list) + _zz_list(opts->list); + if(opts->protect) + _zz_protect(opts->protect); + if(opts->refuse) + _zz_refuse(opts->refuse); + + /* Needed for stdin mode and for copy opmode. */ + _zz_fd_init(); + + /* + * Mode 1: asked to read from the standard input + */ + if(caca_optind >= argc) + { + if(opts->verbose) + { + finfo(stderr, opts, opts->seed); + fprintf(stderr, "reading from stdin\n"); + } + + if(opts->endseed != opts->seed + 1) + { + fprintf(stderr, "%s: seed ranges are incompatible with " + "stdin fuzzing\n", argv[0]); + printf(MOREINFO, argv[0]); + _zz_opts_fini(opts); + return EXIT_FAILURE; + } + + loop_stdin(opts); + } + /* + * Mode 2: asked to launch programs + */ + else + { +#if defined HAVE_REGEX_H + if(cmdline) + { + int dashdash = 0; + + for(i = caca_optind + 1; i < argc; i++) + { + if(dashdash) + include = merge_file(include, argv[i]); + else if(!strcmp("--", argv[i])) + dashdash = 1; + else if(argv[i][0] != '-') + include = merge_file(include, argv[i]); + } + } + + if(include) + setenv("ZZUF_INCLUDE", include, 1); + if(exclude) + setenv("ZZUF_EXCLUDE", exclude, 1); +#endif + + setenv("ZZUF_DEBUG", debug ? debug > 1 ? "2" : "1" : "0", 1); + + if(opts->fuzzing) + setenv("ZZUF_FUZZING", opts->fuzzing, 1); + if(opts->bytes) + setenv("ZZUF_BYTES", opts->bytes, 1); + if(opts->list) + setenv("ZZUF_LIST", opts->list, 1); + if(opts->ports) + setenv("ZZUF_PORTS", opts->ports, 1); + if(opts->allow && opts->allow[0] == '!') + setenv("ZZUF_DENY", opts->allow, 1); + else if(opts->allow) + setenv("ZZUF_ALLOW", opts->allow, 1); + if(opts->protect) + setenv("ZZUF_PROTECT", opts->protect, 1); + if(opts->refuse) + setenv("ZZUF_REFUSE", opts->refuse, 1); +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM + if(opts->maxmem >= 0) + { + char buf[32]; + snprintf(buf, 32, "%i", opts->maxmem); + setenv("ZZUF_MEMORY", buf, 1); + } +#endif + + /* Allocate memory for children handling */ + opts->child = malloc(opts->maxchild * sizeof(struct child)); + for(i = 0; i < opts->maxchild; i++) + { + opts->child[i].status = STATUS_FREE; + memset(opts->child[i].fd, -1, sizeof(opts->child->fd)); + } + opts->nchild = 0; + + /* Create new argv */ + opts->oldargc = argc; + opts->oldargv = argv; + for(i = 0; i < opts->maxchild; i++) + { + int len = argc - caca_optind; + opts->child[i].newargv = malloc((len + 1) * sizeof(char *)); + memcpy(opts->child[i].newargv, argv + caca_optind, + len * sizeof(char *)); + opts->child[i].newargv[len] = (char *)NULL; + } + + /* Main loop */ + while(opts->nchild || opts->seed < opts->endseed) + { + /* Spawn new children, if necessary */ + spawn_children(opts); + + /* Cleanup dead or dying children */ + clean_children(opts); + + /* Read data from children */ + read_children(opts); + + if(opts->maxcrashes && opts->crashes >= opts->maxcrashes + && opts->nchild == 0) + { + if(opts->verbose) + fprintf(stderr, + "zzuf: maximum crash count reached, exiting\n"); + break; + } + + if(opts->maxtime && _zz_time() - opts->starttime >= opts->maxtime + && opts->nchild == 0) + { + if(opts->verbose) + fprintf(stderr, + "zzuf: maximum running time reached, exiting\n"); + break; + } + } + } + + /* Clean up */ + _zz_fd_fini(); + _zz_opts_fini(opts); + +#if defined _WIN32 + DeleteCriticalSection(&_zz_pipe_cs); +#endif + + return opts->crashes ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void loop_stdin(struct opts *opts) +{ + uint8_t md5sum[16]; + struct md5 *ctx = NULL; + int total = 0; + + if(opts->md5) + ctx = _zz_md5_init(); + + _zz_register(0); + + for(;;) + { + uint8_t buf[BUFSIZ]; + int ret, toread = BUFSIZ, off = 0, nw = 0; + + if(opts->maxbytes >= 0) + { + if(total >= opts->maxbytes) + break; + if(total + BUFSIZ >= opts->maxbytes) + toread = opts->maxbytes - total; + } + + ret = read(0, buf, toread); + if(ret <= 0) + break; + + total += ret; + + _zz_fuzz(0, buf, ret); + _zz_addpos(0, ret); + + if(opts->md5) + _zz_md5_add(ctx, buf, ret); + else while(ret) + { + if((nw = write(1, buf + off, (unsigned int)ret)) < 0) + break; + ret -= nw; + off += nw; + } + } + + if(opts->md5) + { + _zz_md5_fini(md5sum, ctx); + finfo(stdout, opts, opts->seed); + fprintf(stdout, "%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x" + "%.02x%.02x%.02x%.02x%.02x%.02x\n", md5sum[0], md5sum[1], + md5sum[2], md5sum[3], md5sum[4], md5sum[5], md5sum[6], + md5sum[7], md5sum[8], md5sum[9], md5sum[10], md5sum[11], + md5sum[12], md5sum[13], md5sum[14], md5sum[15]); + fflush(stdout); + } + + _zz_unregister(0); +} + +static void finfo(FILE *fp, struct opts *opts, uint32_t seed) +{ + if(opts->minratio == opts->maxratio) + fprintf(fp, "zzuf[s=%i,r=%g]: ", seed, opts->minratio); + else + fprintf(fp, "zzuf[s=%i,r=%g:%g]: ", seed, + opts->minratio, opts->maxratio); +} + +#if defined HAVE_REGEX_H +static char *merge_file(char *regex, char *file) +{ + char *newfile = malloc(5 + 2 * strlen(file) + 1 + 1), *tmp = newfile; + + *tmp++ = '('; + *tmp++ = '^'; + *tmp++ = '|'; + *tmp++ = '/'; + *tmp++ = ')'; + while(*file) + { + if(strchr("^.[$()|*+?{\\", *file)) + *tmp++ = '\\'; + *tmp++ = *file++; + } + *tmp++ = '$'; + *tmp++ = '\0'; + + tmp = merge_regex(regex, newfile); + free(newfile); + return tmp; +} + +static char *merge_regex(char *regex, char *string) +{ + regex_t optre; + + if(regex) + { + regex = realloc(regex, strlen(regex) + strlen(string) + 1 + 1); + sprintf(regex + strlen(regex) - 1, "|%s)", string); + } + else + { + regex = malloc(1 + strlen(string) + 1 + 1); + sprintf(regex, "(%s)", string); + } + + if(regcomp(&optre, regex, REG_EXTENDED) != 0) + { + free(regex); + return NULL; + } + regfree(&optre); + + return regex; +} +#endif + +static void spawn_children(struct opts *opts) +{ + int64_t now = _zz_time(); + int i; + + if(opts->nchild == opts->maxchild) + return; /* no slot */ + + if(opts->seed == opts->endseed) + return; /* job finished */ + + if(opts->maxcrashes && opts->crashes >= opts->maxcrashes) + return; /* all jobs crashed */ + + if(opts->maxtime && now - opts->starttime >= opts->maxtime) + return; /* run time exceeded */ + + if(opts->delay > 0 && opts->lastlaunch + opts->delay > now) + return; /* too early */ + + /* Find the empty slot */ + for(i = 0; i < opts->maxchild; i++) + if(opts->child[i].status == STATUS_FREE) + break; + + /* Prepare required files, if necessary */ + if (opts->opmode == OPMODE_COPY) + { + char tmpname[4096]; + char *tmpdir; + FILE *fpin; + int j, k = 0, fdout; + + tmpdir = getenv("TEMP"); + if (!tmpdir || !*tmpdir) + tmpdir = "/tmp"; + + for (j = caca_optind + 1; j < opts->oldargc; j++) + { + fpin = fopen(opts->oldargv[j], "r"); + if (!fpin) + continue; + +#ifdef _WIN32 +# + sprintf(tmpname, "%s/zzuf.$i.XXXXXX", tmpdir, GetCurrentProcessId()); + fdout = _open(mktemp(tmpname), _O_RDWR, 0600); +#else + sprintf(tmpname, "%s/zzuf.%i.XXXXXX", tmpdir, (int)getpid()); + fdout = mkstemp(tmpname); +#endif + if (fdout < 0) + { + fclose(fpin); + continue; + } + + opts->child[i].newargv[j - caca_optind] = strdup(tmpname); + + _zz_register(k); + while(!feof(fpin)) + { + uint8_t buf[BUFSIZ]; + size_t n = fread(buf, 1, BUFSIZ, fpin); + if (n <= 0) + break; + _zz_fuzz(k, buf, n); + _zz_addpos(k, n); + write(fdout, buf, n); + } + _zz_unregister(k); + + fclose(fpin); + close(fdout); + + k++; + } + } + + /* Launch process */ + if (myfork(&opts->child[i], opts) < 0) + { + fprintf(stderr, "error launching `%s'\n", opts->child[i].newargv[0]); + opts->seed++; + /* FIXME: clean up OPMODE_COPY files here */ + return; + } + + /* We’re the parent, acknowledge spawn */ + opts->child[i].date = now; + opts->child[i].bytes = 0; + opts->child[i].seed = opts->seed; + opts->child[i].ratio = _zz_getratio(); + opts->child[i].status = STATUS_RUNNING; + if(opts->md5) + opts->child[i].ctx = _zz_md5_init(); + + if(opts->verbose) + { + finfo(stderr, opts, opts->child[i].seed); + fprintf(stderr, "launched `%s'\n", opts->child[i].newargv[0]); + } + + opts->lastlaunch = now; + opts->nchild++; + opts->seed++; + + _zz_setseed(opts->seed); +} + +static void clean_children(struct opts *opts) +{ +#if defined HAVE_KILL || defined HAVE_WINDOWS_H + int64_t now = _zz_time(); +#endif + int i, j; + +#if defined HAVE_KILL || defined HAVE_WINDOWS_H + /* Terminate children if necessary */ + for(i = 0; i < opts->maxchild; i++) + { + if(opts->child[i].status == STATUS_RUNNING + && opts->maxbytes >= 0 + && opts->child[i].bytes > opts->maxbytes) + { + if(opts->verbose) + { + finfo(stderr, opts, opts->child[i].seed); + fprintf(stderr, "data output exceeded, sending SIGTERM\n"); + } +#if defined HAVE_KILL + kill(opts->child[i].pid, SIGTERM); +#else + /* We must invalidate fd */ + memset(opts->child[i].fd, -1, sizeof(opts->child[i].fd)); + TerminateProcess(opts->child[i].process_handle, 0x0); +#endif + opts->child[i].date = now; + opts->child[i].status = STATUS_SIGTERM; + } + + if(opts->child[i].status == STATUS_RUNNING + && opts->maxusertime >= 0 + && now > opts->child[i].date + opts->maxusertime) + { + if(opts->verbose) + { + finfo(stderr, opts, opts->child[i].seed); + fprintf(stderr, "running time exceeded, sending SIGTERM\n"); + } +#if defined HAVE_KILL + kill(opts->child[i].pid, SIGTERM); +#else + /* We must invalidate fd */ + memset(opts->child[i].fd, -1, sizeof(opts->child[i].fd)); + TerminateProcess(opts->child[i].process_handle, 0x0); +#endif + opts->child[i].date = now; + opts->child[i].status = STATUS_SIGTERM; + } + } + + /* Kill children if necessary (still there after 2 seconds) */ + for(i = 0; i < opts->maxchild; i++) + { + if(opts->child[i].status == STATUS_SIGTERM + && now > opts->child[i].date + 2000000) + { + if(opts->verbose) + { + finfo(stderr, opts, opts->child[i].seed); + fprintf(stderr, "not responding, sending SIGKILL\n"); + } +#if defined HAVE_KILL + kill(opts->child[i].pid, SIGKILL); +#else + TerminateProcess(opts->child[i].process_handle, 0x0); +#endif + opts->child[i].status = STATUS_SIGKILL; + } + } +#endif + + /* Collect dead children */ + for(i = 0; i < opts->maxchild; i++) + { + uint8_t md5sum[16]; +#if defined HAVE_WAITPID + int status; + pid_t pid; +#endif + + if(opts->child[i].status != STATUS_SIGKILL + && opts->child[i].status != STATUS_SIGTERM + && opts->child[i].status != STATUS_EOF) + continue; + +#if defined HAVE_WAITPID + pid = waitpid(opts->child[i].pid, &status, WNOHANG); + if(pid <= 0) + continue; + + if(opts->checkexit && WIFEXITED(status) && WEXITSTATUS(status)) + { + finfo(stderr, opts, opts->child[i].seed); + fprintf(stderr, "exit %i\n", WEXITSTATUS(status)); + opts->crashes++; + } + else if(WIFSIGNALED(status) + && !(WTERMSIG(status) == SIGTERM + && opts->child[i].status == STATUS_SIGTERM)) + { + char const *message = ""; + + if(WTERMSIG(status) == SIGKILL && opts->maxmem >= 0) + message = " (memory exceeded?)"; +# if defined SIGXCPU + else if(WTERMSIG(status) == SIGXCPU && opts->maxcpu >= 0) + message = " (CPU time exceeded?)"; +# endif + else if(WTERMSIG(status) == SIGKILL && opts->maxcpu >= 0) + message = " (CPU time exceeded?)"; + + finfo(stderr, opts, opts->child[i].seed); + fprintf(stderr, "signal %i%s%s\n", + WTERMSIG(status), sig2name(WTERMSIG(status)), message); + opts->crashes++; + } + else if (opts->verbose) + { + finfo(stderr, opts, opts->child[i].seed); + if (WIFSIGNALED(status)) + fprintf(stderr, "signal %i%s\n", + WTERMSIG(status), sig2name(WTERMSIG(status))); + else + fprintf(stderr, "exit %i\n", WEXITSTATUS(status)); + } +#elif defined _WIN32 + { + DWORD exit_code; + if (GetExitCodeProcess(opts->child[i].process_handle, &exit_code)) + { + if (exit_code == STILL_ACTIVE) continue; /* The process is still active, we don't do anything */ + + /* + * The main problem with GetExitCodeProcess is it returns either returned parameter value of + * ExitProcess/TerminateProcess, or the unhandled exception (which is what we're looking for) + */ + switch (exit_code) + { + case EXCEPTION_ACCESS_VIOLATION: fprintf(stderr, "child(%d) unhandled exception: Access Violation\n", opts->child[i].pid); break; + default: fprintf(stderr, "child(%d) exited with code %#08x\n", opts->child[i].pid, exit_code); break; + } + } + + if (opts->child[i].status != STATUS_RUNNING) + { + TerminateProcess(opts->child[i].process_handle, 0); + } + } +#else + /* waitpid() is not available. Don't kill the process. */ + continue; +#endif + + for(j = 0; j < 3; j++) + if(opts->child[i].fd[j] >= 0) + close(opts->child[i].fd[j]); + + if (opts->opmode == OPMODE_COPY) + { + for (j = caca_optind + 1; j < opts->oldargc; j++) + { + if (opts->child[i].newargv[j - caca_optind] != opts->oldargv[j]) + { + unlink(opts->child[i].newargv[j - caca_optind]); + free(opts->child[i].newargv[j - caca_optind]); + opts->child[i].newargv[j - caca_optind] = opts->oldargv[j]; + } + } + } + + if(opts->md5) + { + _zz_md5_fini(md5sum, opts->child[i].ctx); + finfo(stdout, opts, opts->child[i].seed); + fprintf(stdout, "%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x" + "%.02x%.02x%.02x%.02x%.02x%.02x%.02x\n", md5sum[0], + md5sum[1], md5sum[2], md5sum[3], md5sum[4], md5sum[5], + md5sum[6], md5sum[7], md5sum[8], md5sum[9], md5sum[10], + md5sum[11], md5sum[12], md5sum[13], md5sum[14], md5sum[15]); + fflush(stdout); + } + opts->child[i].status = STATUS_FREE; + opts->nchild--; + } +} + +#ifdef _WIN32 + +/* This structure contains useful information about data sent from fuzzed applications */ +struct child_overlapped +{ + OVERLAPPED overlapped; + char buf[BUFSIZ]; + struct opts * opts; + int child_no; + int fd_no; +}; + +/* This callback is called when fuzzed applications write in fd out, err or debug */ +static void _stdcall read_child(DWORD err_code, DWORD nbr_of_bytes_transfered, LPOVERLAPPED overlapped) +{ + struct child_overlapped * co = (struct child_overlapped *)overlapped; + + /* TODO: handle more cases like ERROR_MORE_DATA */ + if (err_code != ERROR_SUCCESS) return; + + EnterCriticalSection(&_zz_pipe_cs); + switch (co->fd_no) + { + case 0: /* debug fd */ + write(1, "dbg: ", 4); + case 1: /* out */ + write(1, co->buf, nbr_of_bytes_transfered); break; + case 2: /* err */ + write(2, co->buf, nbr_of_bytes_transfered); break; + default: break; + } + LeaveCriticalSection(&_zz_pipe_cs); + + if(co->fd_no != 0) /* either out or err fd */ + co->opts->child[co->child_no].bytes += nbr_of_bytes_transfered; + + if(co->opts->md5 && co->fd_no == 2) + _zz_md5_add(co->opts->child[co->child_no].ctx, co->buf, nbr_of_bytes_transfered); + + free(co); /* clean up allocated data */ +} + +/* Since on windows select doesn't support file HANDLE, we use IOCP */ +static void read_children(struct opts *opts) +{ + size_t i, j; + HANDLE *children_handle, * cur_child_handle; + size_t fd_number = opts->maxchild * 3; + + cur_child_handle = children_handle = malloc(sizeof(*children_handle) * fd_number); + + for(i = 0; i < fd_number; i++) + children_handle[i] = INVALID_HANDLE_VALUE; + + /* XXX: cute (i, j) iterating hack */ + for(i = 0, j = 0; i < (size_t)opts->maxchild; i += (j == 2), j = (j + 1) % 3) + { + struct child_overlapped * co; + HANDLE h = (opts->child[i].fd[j] == -1) ? INVALID_HANDLE_VALUE : (HANDLE)_get_osfhandle(opts->child[i].fd[j]); + + if(opts->child[i].status != STATUS_RUNNING + || opts->child[i].fd[j] == -1 + || h == INVALID_HANDLE_VALUE) + { + fd_number--; + continue; + } + + co = malloc(sizeof(*co)); + ZeroMemory(co, sizeof(*co)); + *cur_child_handle = co->overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + co->child_no = i; + co->fd_no = j; + co->opts = opts; + + if(!ReadFileEx(h, co->buf, sizeof(co->buf), (LPOVERLAPPED)co, read_child)) + { + /* End of file reached */ + close(opts->child[i].fd[j]); + opts->child[i].fd[j] = -1; + + if(opts->child[i].fd[0] == -1 + && opts->child[i].fd[1] == -1 + && opts->child[i].fd[2] == -1) + opts->child[i].status = STATUS_EOF; + } + cur_child_handle++; + } + + if (fd_number == 0) return; + + /* FIXME: handle error */ + WaitForMultipleObjectsEx(fd_number, children_handle, FALSE, 1000, TRUE); +} + +#else +static void read_children(struct opts *opts) +{ + struct timeval tv; + fd_set fdset; + int i, j, ret, maxfd = 0; + + /* Read data from all sockets */ + FD_ZERO(&fdset); + for(i = 0; i < opts->maxchild; i++) + { + if(opts->child[i].status != STATUS_RUNNING) + continue; + + for(j = 0; j < 3; j++) + ZZUF_FD_SET(opts->child[i].fd[j], &fdset, maxfd); + } + tv.tv_sec = 0; + tv.tv_usec = 1000; + + errno = 0; + ret = select(maxfd + 1, &fdset, NULL, NULL, &tv); + if(ret < 0 && errno) + perror("select"); + if(ret <= 0) + return; + + /* XXX: cute (i, j) iterating hack */ + for(i = 0, j = 0; i < opts->maxchild; i += (j == 2), j = (j + 1) % 3) + { + uint8_t buf[BUFSIZ]; + + if(opts->child[i].status != STATUS_RUNNING) + continue; + + if(!ZZUF_FD_ISSET(opts->child[i].fd[j], &fdset)) + continue; + + ret = read(opts->child[i].fd[j], buf, BUFSIZ - 1); + if(ret > 0) + { + /* We got data */ + if(j != 0) + opts->child[i].bytes += ret; + + if(opts->md5 && j == 2) + _zz_md5_add(opts->child[i].ctx, buf, ret); + else if(!opts->quiet || j == 0) + write((j < 2) ? STDERR_FILENO : STDOUT_FILENO, buf, ret); + } + else if(ret == 0) + { + /* End of file reached */ + close(opts->child[i].fd[j]); + opts->child[i].fd[j] = -1; + + if(opts->child[i].fd[0] == -1 + && opts->child[i].fd[1] == -1 + && opts->child[i].fd[2] == -1) + opts->child[i].status = STATUS_EOF; + } + } +} +#endif + +#if !defined HAVE_SETENV +static void setenv(char const *name, char const *value, int overwrite) +{ + char *str; + + if(!overwrite && getenv(name)) + return; + + str = malloc(strlen(name) + 1 + strlen(value) + 1); + sprintf(str, "%s=%s", name, value); + putenv(str); +} +#endif + +#if defined HAVE_WAITPID +static char const *sig2name(int signum) +{ + switch(signum) + { +#ifdef SIGQUIT + case SIGQUIT: return " (SIGQUIT)"; /* 3 */ +#endif + case SIGILL: return " (SIGILL)"; /* 4 */ +#ifdef SIGTRAP + case SIGTRAP: return " (SIGTRAP)"; /* 5 */ +#endif + case SIGABRT: return " (SIGABRT)"; /* 6 */ +#ifdef SIGBUS + case SIGBUS: return " (SIGBUS)"; /* 7 */ +#endif + case SIGFPE: return " (SIGFPE)"; /* 8 */ + case SIGSEGV: return " (SIGSEGV)"; /* 11 */ + case SIGPIPE: return " (SIGPIPE)"; /* 13 */ +#ifdef SIGEMT + case SIGEMT: return " (SIGEMT)"; /* ? */ +#endif +#ifdef SIGXCPU + case SIGXCPU: return " (SIGXCPU)"; /* 24 */ +#endif +#ifdef SIGXFSZ + case SIGXFSZ: return " (SIGXFSZ)"; /* 25 */ +#endif +#ifdef SIGSYS + case SIGSYS: return " (SIGSYS)"; /* 31 */ +#endif + } + + return ""; +} +#endif + +static void version(void) +{ + printf("zzuf %s\n", PACKAGE_VERSION); + printf("Copyright (C) 2002-2010 Sam Hocevar \n"); + printf("This program is free software. It comes without any warranty, to the extent\n"); + printf("permitted by applicable law. You can redistribute it and/or modify it under\n"); + printf("the terms of the Do What The Fuck You Want To Public License, Version 2, as\n"); + printf("published by Sam Hocevar. See for more details.\n"); + printf("\n"); + printf("Written by Sam Hocevar. Report bugs to .\n"); +} + +static void usage(void) +{ +#if defined HAVE_REGEX_H + printf("Usage: zzuf [-aAcdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n"); +#else + printf("Usage: zzuf [-aAdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n"); +#endif + printf(" [-f mode] [-D delay] [-j jobs] [-C crashes] [-B bytes] [-a list]\n"); + printf(" [-t seconds]"); +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU + printf( " [-T seconds]"); +#endif +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM + printf( " [-M mebibytes]"); +#endif + printf( " [-b ranges] [-p ports]\n"); + printf(" [-P protect] [-R refuse] [-l list]"); +#if defined HAVE_REGEX_H + printf( " [-I include] [-E exclude]"); +#endif + printf(" [-O mode]\n"); + printf("\n"); + printf(" [PROGRAM [--] [ARGS]...]\n"); + printf(" zzuf -h | --help\n"); + printf(" zzuf -V | --version\n"); + printf("Run PROGRAM with optional arguments ARGS and fuzz its input.\n"); + printf("\n"); + printf("Mandatory arguments to long options are mandatory for short options too.\n"); + printf(" -a, --allow only fuzz network input for IPs in \n"); + printf(" ... ! do not fuzz network input for IPs in \n"); + printf(" -A, --autoinc increment seed each time a new file is opened\n"); + printf(" -b, --bytes only fuzz bytes at offsets within \n"); + printf(" -B, --max-bytes kill children that output more than bytes\n"); +#if defined HAVE_REGEX_H + printf(" -c, --cmdline only fuzz files specified in the command line\n"); +#endif + printf(" -C, --max-crashes stop after children have crashed (default 1)\n"); + printf(" -d, --debug print debug messages (twice for more verbosity)\n"); + printf(" -D, --delay delay between forks\n"); +#if defined HAVE_REGEX_H + printf(" -E, --exclude do not fuzz files matching \n"); +#endif + printf(" -f, --fuzzing use fuzzing mode ([xor] set unset)\n"); + printf(" -i, --stdin fuzz standard input\n"); +#if defined HAVE_REGEX_H + printf(" -I, --include only fuzz files matching \n"); +#endif + printf(" -j, --jobs number of simultaneous jobs (default 1)\n"); + printf(" -l, --list only fuzz Nth descriptor with N in \n"); + printf(" -m, --md5 compute the output's MD5 hash\n"); +#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM + printf(" -M, --max-memory maximum child virtual memory in MiB (default %u)\n", DEFAULT_MEM); +#endif + printf(" -n, --network fuzz network input\n"); + printf(" -O, --opmode use operating mode ([preload] copy)\n"); + printf(" -p, --ports only fuzz network destination ports in \n"); + printf(" -P, --protect protect bytes and characters in \n"); + printf(" -q, --quiet do not print children's messages\n"); + printf(" -r, --ratio bit fuzzing ratio (default %g)\n", DEFAULT_RATIO); + printf(" ... specify a ratio range\n"); + printf(" -R, --refuse refuse bytes and characters in \n"); + printf(" -s, --seed random seed (default %i)\n", DEFAULT_SEED); + printf(" ... specify a seed range\n"); + printf(" -S, --signal prevent children from diverting crashing signals\n"); + printf(" -t, --max-time stop spawning children after seconds\n"); + printf(" -T, --max-cputime kill children that use more than CPU seconds\n"); + printf(" -U, --max-usertime kill children that run for more than seconds\n"); + printf(" -v, --verbose print information during the run\n"); + printf(" -x, --check-exit report processes that exit with a non-zero status\n"); + printf(" -h, --help display this help and exit\n"); + printf(" -V, --version output version information and exit\n"); + printf("\n"); + printf("Written by Sam Hocevar. Report bugs to .\n"); +} +