#include "libhfnetdriver/netdriver.h" #include <arpa/inet.h> #include <errno.h> #include <inttypes.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #if defined(_HF_ARCH_LINUX) #include <sched.h> #endif /* defined(_HF_ARCH_LINUX) */ #include "honggfuzz.h" #include "libhfcommon/common.h" #include "libhfcommon/files.h" #include "libhfcommon/log.h" #include "libhfcommon/ns.h" #include "libhfcommon/util.h" __attribute__((visibility("default"))) __attribute__((used)) const char *const LIBHFNETDRIVER_module_netdriver = _HF_NETDRIVER_SIG; #define HFND_TCP_PORT_ENV "HFND_TCP_PORT" #define HFND_SKIP_FUZZING_ENV "HFND_SKIP_FUZZING" static char *initial_server_argv[] = {"fuzzer", NULL}; static struct { int argc_server; char **argv_server; uint16_t tcp_port; sa_family_t sa_family; } hfnd_globals = { .argc_server = 1, .argv_server = initial_server_argv, .tcp_port = 0, .sa_family = AF_UNSPEC, }; __attribute__((weak)) int HonggfuzzNetDriver_main( int argc HF_ATTR_UNUSED, char **argv HF_ATTR_UNUSED) { LOG_F("The HonggfuzzNetDriver_main function was not defined in your code"); return EXIT_FAILURE; } static void *netDriver_mainProgram(void *unused HF_ATTR_UNUSED) { int ret = HonggfuzzNetDriver_main(hfnd_globals.argc_server, hfnd_globals.argv_server); LOG_I("Honggfuzz Net Driver (pid=%d): HonggfuzzNetDriver_main() function exited with: %d", (int)getpid(), ret); _exit(ret); } static void netDriver_startOriginalProgramInThread(void) { pthread_t t; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, 1024ULL * 1024ULL * 8ULL); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create(&t, &attr, netDriver_mainProgram, NULL) != 0) { PLOG_F("Couldn't create the 'netDriver_mainProgram' thread"); } } static void netDriver_initNsIfNeeded(void) { static bool initialized = false; if (initialized) { return; } initialized = true; #if defined(_HF_ARCH_LINUX) if (!nsEnter(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS)) { LOG_F("nsEnter(CLONE_NEWUSER|CLONE_NEWNET|CLONE_NEWNS|CLONE_NEWIPC|CLONE_NEWUTS) failed"); } if (!nsIfaceUp("lo")) { LOG_F("nsIfaceUp('lo') failed"); } if (mkdir(HFND_TMP_DIR_OLD, 0755) == -1 && errno != EEXIST) { PLOG_F("mkdir('%s', 0755)", HFND_TMP_DIR_OLD); } if (mkdir(HFND_TMP_DIR, 0755) == -1 && errno != EEXIST) { PLOG_F("mkdir('%s', 0755)", HFND_TMP_DIR); } if (!nsMountTmpfs(HFND_TMP_DIR_OLD)) { LOG_F("nsMountTmpfs('%s') failed", HFND_TMP_DIR_OLD); } if (!nsMountTmpfs(HFND_TMP_DIR)) { LOG_F("nsMountTmpfs('%s') failed", HFND_TMP_DIR); } return; #endif /* defined(_HF_ARCH_LINUX) */ LOG_W("Honggfuzz Net Driver (pid=%d): Namespaces not enabled for this OS platform", (int)getpid()); } /* * Try to bind the client socket to a random loopback address, to avoid problems with exhausted * ephemeral ports. We run out of them, because the TIME_WAIT state is imposed on recently closed * TCP connections originating from the same IP address (127.0.0.1), and connecting to the singular * IP address (again, 127.0.0.1) on a single port */ static void netDriver_bindToRndLoopback(int sock, sa_family_t sa_family) { if (sa_family != AF_INET) { return; } const struct sockaddr_in bsaddr = { .sin_family = AF_INET, .sin_port = htons(0), .sin_addr.s_addr = htonl((((uint32_t)util_rnd64()) & 0x00FFFFFF) | 0x7F000000), }; if (bind(sock, (struct sockaddr *)&bsaddr, sizeof(bsaddr)) == -1) { PLOG_W("Could not bind to a random IPv4 Loopback address"); } } static int netDriver_sockConnAddr(const struct sockaddr *addr, socklen_t socklen) { int sock = socket(addr->sa_family, SOCK_STREAM, 0); if (sock == -1) { PLOG_D("socket(type=%d for dst_addr='%s', SOCK_STREAM, 0)", addr->sa_family, files_sockAddrToStr(addr)); return -1; } int val = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, (socklen_t)sizeof(val)) == -1) { PLOG_W("setsockopt(sock=%d, SOL_SOCKET, SO_REUSEADDR, 1)", sock); } #if defined(SOL_TCP) && defined(TCP_NODELAY) val = 1; if (setsockopt(sock, SOL_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)) == -1) { PLOG_W("setsockopt(sock=%d, SOL_TCP, TCP_NODELAY, 1)", sock); } #endif /* defined(SOL_TCP) && defined(TCP_NODELAY) */ netDriver_bindToRndLoopback(sock, addr->sa_family); LOG_D("Connecting to '%s'", files_sockAddrToStr(addr)); if (TEMP_FAILURE_RETRY(connect(sock, addr, socklen)) == -1) { PLOG_W("connect(addr='%s')", files_sockAddrToStr(addr)); close(sock); return -1; } return sock; } int netDriver_sockConnLoopback(sa_family_t sa_family, uint16_t portno) { if (portno < 1) { LOG_F("Specified TCP port (%d) cannot be < 1", portno); } if (sa_family == AF_INET) { /* IPv4's 127.0.0.1 */ const struct sockaddr_in saddr4 = { .sin_family = AF_INET, .sin_port = htons(portno), .sin_addr.s_addr = htonl(INADDR_LOOPBACK), }; return netDriver_sockConnAddr((const struct sockaddr *)&saddr4, sizeof(saddr4)); } if (sa_family == AF_INET6) { /* IPv6's ::1 */ const struct sockaddr_in6 saddr6 = { .sin6_family = AF_INET6, .sin6_port = htons(portno), .sin6_flowinfo = 0, .sin6_addr = in6addr_loopback, .sin6_scope_id = 0, }; return netDriver_sockConnAddr((const struct sockaddr *)&saddr6, sizeof(saddr6)); } LOG_E("Unknown SA_FAMILY=%d specified", (int)sa_family); return -1; } /* * Decide which TCP port should be used for sending inputs */ __attribute__((weak)) uint16_t HonggfuzzNetDriverPort( int argc HF_ATTR_UNUSED, char **argv HF_ATTR_UNUSED) { /* Return the default port (8080) */ return 8080; } /* * The return value is a number of arguments passed returned to libfuzzer (if used) * * Define this function in your code to describe which arguments are passed to the fuzzed * TCP server, and which to the fuzzing engine. */ __attribute__((weak)) int HonggfuzzNetDriverArgsForServer( int argc, char **argv, int *server_argc, char ***server_argv) { /* If the used fuzzer is honggfuzz, simply pass all arguments to the TCP server */ __attribute__((weak)) int HonggfuzzMain(int argc, char **argv); if (HonggfuzzMain) { *server_argc = argc; *server_argv = argv; return argc; } /* * For other fuzzing engines: * Split: ./httpdserver -max_input=10 -- --config /etc/httpd.confg * into: * The fuzzing engine (e.g. libfuzzer) will see "./httpdserver -max_input=10", * The httpdserver will see: "./httpdserver --config /etc/httpd.confg" */ for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "--") == 0) { /* Replace '--' with argv[0] */ argv[i] = argv[0]; *server_argc = argc - i; *server_argv = &argv[i]; return i; } } LOG_I("Honggfuzz Net Driver (pid=%d): No '--' was found in the commandline, and therefore no " "arguments will be passed to the TCP server program", (int)getpid()); *server_argc = 1; *server_argv = &argv[0]; return argc; } static void netDriver_waitForServerReady(uint16_t portno) { for (;;) { int fd = -1; fd = netDriver_sockConnLoopback(AF_INET, portno); if (fd >= 0) { hfnd_globals.sa_family = AF_INET; close(fd); return; } fd = netDriver_sockConnLoopback(AF_INET6, portno); if (fd >= 0) { hfnd_globals.sa_family = AF_INET6; close(fd); return; } LOG_I( "Honggfuzz Net Driver (pid=%d): Waiting for the TCP server process to start accepting " "connections at TCP4:127.0.0.1:%" PRIu16 " or at TCP6:[::1]:%" PRIu16 ". Sleeping for 0.5 seconds ...", (int)getpid(), portno, portno); usleep(500000U); } } uint16_t netDriver_getTCPPort(int argc, char **argv) { const char *port_str = getenv(HFND_TCP_PORT_ENV); if (port_str) { errno = 0; signed long portsl = strtol(port_str, NULL, 0); if (errno != 0) { PLOG_F("Couldn't convert '%s'='%s' to a number", HFND_TCP_PORT_ENV, port_str); } if (portsl < 1) { LOG_F("Specified TCP port '%s'='%s' (%ld) cannot be < 1", HFND_TCP_PORT_ENV, port_str, portsl); } if (portsl > 65535) { LOG_F("Specified TCP port '%s'='%s' (%ld) cannot be > 65535", HFND_TCP_PORT_ENV, port_str, portsl); } return (uint16_t)portsl; } return HonggfuzzNetDriverPort(argc, argv); } __attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv) { if (getenv(HFND_SKIP_FUZZING_ENV)) { LOG_I( "Honggfuzz Net Driver (pid=%d): '%s' is set, skipping fuzzing, calling main() directly", getpid(), HFND_SKIP_FUZZING_ENV); if (!HonggfuzzNetDriver_main) { LOG_F("Honggfuzz Net Driver (pid=%d): HonggfuzzNetDriver_main was not defined in your " "code", getpid()); } exit(HonggfuzzNetDriver_main(*argc, *argv)); } /* Make sure LIBHFNETDRIVER_module_netdriver (NetDriver signature) is used */ LOG_D("Module: %s", LIBHFNETDRIVER_module_netdriver); hfnd_globals.tcp_port = netDriver_getTCPPort(*argc, *argv); *argc = HonggfuzzNetDriverArgsForServer( *argc, *argv, &hfnd_globals.argc_server, &hfnd_globals.argv_server); LOG_I( "Honggfuzz Net Driver (pid=%d): TCP port %d will be used. You can change the server's TCP " "port by setting the %s envvar", (int)getpid(), hfnd_globals.tcp_port, HFND_TCP_PORT_ENV); netDriver_initNsIfNeeded(); netDriver_startOriginalProgramInThread(); netDriver_waitForServerReady(hfnd_globals.tcp_port); LOG_I("Honggfuzz Net Driver (pid=%d): The TCP server process is ready to accept connections at " "%s:%" PRIu16 ". TCP fuzzing starts now!", (int)getpid(), (hfnd_globals.sa_family == AF_INET ? "TCP4:127.0.0.1" : "TCP6:[::1]"), hfnd_globals.tcp_port); return 0; } __attribute__((weak)) int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { int sock = netDriver_sockConnLoopback(hfnd_globals.sa_family, hfnd_globals.tcp_port); if (sock == -1) { LOG_F("Couldn't connect to the server TCP port"); } if (!files_sendToSocket(sock, buf, len)) { PLOG_E("files_sendToSocket(sock=%d, len=%zu) failed", sock, len); close(sock); return 0; } /* * Indicate EOF (via the FIN flag) to the TCP server * * Well-behaved TCP servers should process the input and respond/close the TCP connection at * this point */ if (TEMP_FAILURE_RETRY(shutdown(sock, SHUT_WR)) == -1) { if (errno == ENOTCONN) { close(sock); return 0; } PLOG_F("shutdown(sock=%d, SHUT_WR)", sock); } /* * Try to read data from the server, assuming that an early TCP close would sometimes cause the * TCP server to drop the input data, instead of processing it. Use BSS to avoid putting * pressure on the stack size */ static char b[1024ULL * 1024ULL * 4ULL]; while (TEMP_FAILURE_RETRY(recv(sock, b, sizeof(b), MSG_WAITALL)) > 0) ; close(sock); return 0; }