#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;
|
}
|