Add utility program to help run fuzzers against isoping.
host-isoping_fuzz reads packets from standard input, then sends the data
to the isoping server over a socket. This lets fuzzers like afl easily
pass in modified data, while covering as much of the real code as
possible. It also overrides the non-deterministic parts of the server
such as periodic secret rotation and cookie validation so as to not
confuse the fuzzers.
Change-Id: I991add34d65e159036e7279932dd15c6c68a200d
diff --git a/cmds/Makefile b/cmds/Makefile
index 43362dc..b8ff87e 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -56,7 +56,7 @@
wait-until-created \
watch-dir
-HOST_TARGETS=$(addprefix host-,$(TARGETS))
+HOST_TARGETS=$(addprefix host-,$(TARGETS)) host-isoping_fuzz
LIB_TARGETS=\
stdoutline.so
HOST_TEST_TARGETS=\
@@ -188,8 +188,9 @@
host-isoping: host-isoping.o host-isoping_main.o
host-isoping_test.o: CXXFLAGS += -D WVTEST_CONFIGURED -I ../wvtest/cpp
host-isoping_test.o: isoping.cc
-host-isoping_test: LIBS+=$(HOST_LIBS) -lm -lstdc++ -lcrypto
+host-isoping_test host-isoping_fuzz: LIBS+=$(HOST_LIBS) -lm -lstdc++ -lcrypto
host-isoping_test: host-isoping_test.o host-isoping.o host-wvtestmain.o host-wvtest.o
+host-isoping_fuzz: host-isoping.o host-isoping_fuzz.o
host-isostream isostream: LIBS+=$(RT)
host-diskbench diskbench: LIBS+=-lpthread $(RT)
host-dnsck: LIBS+=$(HOST_LIBS) -lcares $(RT)
diff --git a/cmds/isoping.cc b/cmds/isoping.cc
index 18d73f4..48999c3 100644
--- a/cmds/isoping.cc
+++ b/cmds/isoping.cc
@@ -159,6 +159,19 @@
return p.first;
}
+Sessions::Sessions()
+ : md(EVP_sha256()),
+ rng(std::random_device()()),
+ cookie_epoch(0),
+ last_secret_update_time(0) {
+ NewRandomCookieSecret();
+ EVP_MD_CTX_init(&digest_context);
+}
+
+Sessions::~Sessions() {
+ EVP_MD_CTX_cleanup(&digest_context);
+}
+
bool Sessions::CalculateCookie(Packet *p, struct sockaddr_storage *remoteaddr,
size_t remoteaddr_len) {
return CalculateCookieWithSecret(p, remoteaddr, remoteaddr_len,
@@ -206,13 +219,13 @@
Packet golden;
golden.packet_type = PACKET_TYPE_HANDSHAKE;
golden.usec_per_pkt = p->usec_per_pkt;
- if (p->data.handshake.cookie_epoch == cookie_epoch) {
- CalculateCookieWithSecret(&golden, addr, addr_len, cookie_secret,
- sizeof(cookie_secret));
- } else {
- CalculateCookieWithSecret(&golden, addr, addr_len, prev_cookie_secret,
- sizeof(prev_cookie_secret));
+ unsigned char *secret = cookie_secret;
+ size_t secret_len = sizeof(cookie_secret);
+ if (p->data.handshake.cookie_epoch == prev_cookie_epoch) {
+ secret = prev_cookie_secret;
+ secret_len = sizeof(prev_cookie_secret);
}
+ CalculateCookieWithSecret(&golden, addr, addr_len, secret, secret_len);
DLOG("Handshake: cookie epoch=%d, cookie=0x",
p->data.handshake.cookie_epoch);
debug_print_hex(p->data.handshake.cookie, sizeof(p->data.handshake.cookie));
@@ -229,12 +242,15 @@
return true;
}
-void Sessions::MaybeRotateCookieSecrets() {
- // Round off the unix timestamp to 64 seconds as an epoch, so we don't have to
- // track which ones we've already used.
- uint32_t new_epoch = time(NULL) >> 6;
- if (new_epoch != cookie_epoch) {
- RotateCookieSecrets(new_epoch);
+void Sessions::MaybeRotateCookieSecrets(uint32_t now, int is_server) {
+ if (is_server && (now - last_secret_update_time) > 1000000) {
+ // Round off the unix timestamp to 64 seconds as an epoch, so we don't have
+ // to track which ones we've already used.
+ uint32_t new_epoch = time(NULL) >> 6;
+ if (new_epoch != cookie_epoch) {
+ RotateCookieSecrets(new_epoch);
+ }
+ last_secret_update_time = now;
}
}
@@ -897,10 +913,11 @@
s->last_rxtime = rxtime;
}
-int isoping_main(int argc, char **argv) {
+int isoping_main(int argc, char **argv, Sessions *sessions, int extrasock) {
+ assert(sessions != NULL);
+
struct sockaddr_in6 listenaddr;
struct addrinfo *ai = NULL;
- int sock = -1;
setvbuf(stdout, NULL, _IOLBF, 0);
@@ -943,7 +960,7 @@
}
}
- sock = socket(PF_INET6, SOCK_DGRAM, 0);
+ int sock = socket(PF_INET6, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return 1;
@@ -951,7 +968,6 @@
int is_server;
uint32_t now = ustime(); // current time
- Sessions sessions;
if (argc - optind == 0) {
is_server = 1;
@@ -988,8 +1004,9 @@
perror("connect");
return 1;
}
- sessions.NewSession(now, 1e6 / packets_per_sec,
- (struct sockaddr_storage *)ai->ai_addr, ai->ai_addrlen);
+ sessions->NewSession(now, 1e6 / packets_per_sec,
+ (struct sockaddr_storage *)ai->ai_addr,
+ ai->ai_addrlen);
} else {
usage_and_die(argv[0]);
}
@@ -1018,24 +1035,29 @@
act.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &act, NULL);
- uint32_t last_secret_update_time = 0;
-
while (!want_to_die) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
+ if (extrasock > 0) {
+ FD_SET(extrasock, &rfds);
+ }
struct timeval tv;
tv.tv_sec = 0;
now = ustime();
- if (sessions.next_sends.size() == 0 ||
- DIFF(sessions.next_send_time(), now) < 0) {
+ if (sessions->next_sends.size() == 0 ||
+ DIFF(sessions->next_send_time(), now) < 0 ||
+ extrasock > 0) {
tv.tv_usec = 0;
} else {
- tv.tv_usec = DIFF(sessions.next_send_time(), now);
+ tv.tv_usec = DIFF(sessions->next_send_time(), now);
}
- int nfds = select(sock + 1, &rfds, NULL, NULL,
- sessions.next_sends.size() > 0 ? &tv : NULL);
+ struct timeval *tvp = NULL;
+ if (sessions->next_sends.size() > 0 || extrasock > 0) {
+ tvp = &tv;
+ }
+ int nfds = select(std::max(sock, extrasock) + 1, &rfds, NULL, NULL, tvp);
now = ustime();
if (nfds < 0 && errno != EINTR) {
perror("select");
@@ -1043,29 +1065,32 @@
}
// Periodically check if the cookie secrets need updating.
- if (is_server && (now - last_secret_update_time) > 1000000) {
- sessions.MaybeRotateCookieSecrets();
- last_secret_update_time = now;
- }
+ sessions->MaybeRotateCookieSecrets(now, is_server);
- int err = send_waiting_packets(&sessions, sock, now, is_server);
+ int err = send_waiting_packets(sessions, sock, now, is_server);
if (err != 0) {
return err;
}
if (nfds > 0) {
- err = read_incoming_packet(&sessions, sock, now, is_server);
+ int s = FD_ISSET(sock, &rfds) ? sock : extrasock;
+ err = read_incoming_packet(sessions, s, now, is_server);
if (!is_server && err == ECONNREFUSED) return 2;
if (err != 0) {
continue;
}
}
+
+ if (extrasock > 0 && nfds == 0) {
+ DLOG("read all data from extrasock, exiting\n");
+ exit(0);
+ }
}
// TODO(pmccurdy): Separate out per-client and global stats, print stats for
// the server when each client disconnects.
if (!is_server) {
- Session &s = sessions.session_map.begin()->second;
+ Session &s = sessions->session_map.begin()->second;
printf("\n---\n");
printf("tx: min/avg/max/mdev = %.2f/%.2f/%.2f/%.2f ms\n",
s.lat_tx_min / 1000.0,
diff --git a/cmds/isoping.h b/cmds/isoping.h
index 33a4438..8ff819d 100644
--- a/cmds/isoping.h
+++ b/cmds/isoping.h
@@ -128,22 +128,13 @@
const SessionMap::iterator &rhs);
};
-struct Sessions {
+class Sessions {
public:
- Sessions()
- : md(EVP_sha256()),
- rng(std::random_device()()),
- cookie_epoch(0) {
- NewRandomCookieSecret();
- EVP_MD_CTX_init(&digest_context);
- }
-
- ~Sessions() {
- EVP_MD_CTX_cleanup(&digest_context);
- }
+ Sessions();
+ ~Sessions();
// Rotates the cookie secrets if they haven't been changed in a while.
- void MaybeRotateCookieSecrets();
+ virtual void MaybeRotateCookieSecrets(uint32_t now, int is_server);
// Rotate the cookie secrets using the given epoch directly. Only for use in
// unit tests.
@@ -156,8 +147,8 @@
size_t remoteaddr_len);
// Returns true if the packet contains a handshake packet with a valid cookie.
- bool ValidateCookie(Packet *p, struct sockaddr_storage *addr,
- socklen_t addr_len);
+ virtual bool ValidateCookie(Packet *p, struct sockaddr_storage *addr,
+ socklen_t addr_len);
SessionMap::iterator NewSession(uint32_t first_send,
uint32_t usec_per_pkt,
@@ -178,7 +169,7 @@
std::priority_queue<SessionMap::iterator, std::vector<SessionMap::iterator>,
CompareNextSend> next_sends;
- private:
+ protected:
void NewRandomCookieSecret();
bool CalculateCookieWithSecret(Packet *p, struct sockaddr_storage *remoteaddr,
size_t remoteaddr_len, unsigned char *secret,
@@ -189,6 +180,7 @@
const EVP_MD *md;
std::mt19937_64 rng;
uint32_t cookie_epoch;
+ uint32_t last_secret_update_time;
unsigned char cookie_secret[COOKIE_SECRET_SIZE];
uint32_t prev_cookie_epoch;
unsigned char prev_cookie_secret[COOKIE_SECRET_SIZE];
@@ -231,7 +223,8 @@
void set_packets_per_sec(double new_pps);
// Parses arguments and runs the main loop. Distinct from main() for unit test
-// purposes.
-int isoping_main(int argc, char **argv);
+// and fuzz test purposes. extrasock is an additional socket to read from, used
+// for tests. Set to -1 if unused.
+int isoping_main(int argc, char **argv, Sessions *sessions, int extrasock);
#endif
diff --git a/cmds/isoping_fuzz.cc b/cmds/isoping_fuzz.cc
new file mode 100644
index 0000000..17f30da
--- /dev/null
+++ b/cmds/isoping_fuzz.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* A version of isoping_main.cc to be used when fuzzing. Reads the fuzz test
+ * case from standard input, writes it to the socket, then exits once processing
+ * is finished.*/
+
+#include "isoping.h"
+
+#include <netdb.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <thread>
+#include <time.h>
+#include <unistd.h>
+
+// Removes sources of nondeterminism from the Sessions code, so fuzzers can
+// detect which code paths are affected by inputs.
+class DeterministicSessions : public Sessions {
+ public:
+ DeterministicSessions() : Sessions() {
+ // Make the cookie secret data deterministic.
+ prev_cookie_epoch = 1;
+ cookie_epoch = 2;
+ memset(&prev_cookie_secret, 0, sizeof(prev_cookie_secret));
+ memset(&cookie_secret, 0, sizeof(cookie_secret));
+ prev_cookie_secret[0] = 1;
+ cookie_secret[0] = 2;
+ }
+ ~DeterministicSessions() {}
+
+ // Don't rotate the cookie secrets, it confuses the fuzzer.
+ virtual void MaybeRotateCookieSecrets(uint32_t now, int is_server) {
+ }
+
+ // Force the incoming cookie to be valid, then call the real validation
+ // routine. This ensures we test the real routine, without the fuzzer having
+ // to generate valid cookies on its own.
+ virtual bool ValidateCookie(Packet *p, struct sockaddr_storage *addr,
+ socklen_t addr_len) {
+ Packet golden;
+ golden.packet_type = PACKET_TYPE_HANDSHAKE;
+ golden.usec_per_pkt = p->usec_per_pkt;
+ p->data.handshake.cookie_epoch = cookie_epoch;
+ CalculateCookieWithSecret(&golden, addr, addr_len, cookie_secret,
+ sizeof(cookie_secret));
+ memcpy(p->data.handshake.cookie, golden.data.handshake.cookie, COOKIE_SIZE);
+ return Sessions::ValidateCookie(p, addr, addr_len);
+ }
+};
+
+int main(int argc, char **argv) {
+ DeterministicSessions dsessions;
+ // Read data from stdin
+ fprintf(stderr, "Running fuzz code.\n");
+ unsigned char buf[10 * sizeof(Packet)];
+ memset(buf, 0, sizeof(buf));
+ int insize = read(0, buf, sizeof(buf));
+ fprintf(stderr, "Read %d bytes from stdin.\n", insize);
+
+ struct addrinfo hints;
+ struct addrinfo *res;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE | AI_V4MAPPED;
+ int err = getaddrinfo(NULL, "0", &hints, &res);
+ if (err != 0) {
+ perror("getaddrinfo");
+ return 1;
+ }
+
+ int sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (sock < 0) {
+ perror("socket");
+ return 2;
+ }
+
+ if (bind(sock, res->ai_addr, res->ai_addrlen)) {
+ perror("bind");
+ return 3;
+ }
+
+ // Figure out the local port we got.
+ struct sockaddr_storage listenaddr;
+ socklen_t listenaddr_len = sizeof(listenaddr);
+ memset(&listenaddr, 0, listenaddr_len);
+ if (getsockname(sock, (struct sockaddr *)&listenaddr, &listenaddr_len)) {
+ perror("getsockname");
+ return 4;
+ }
+
+ // Send each incoming packet from a different client port.
+ while (insize > 0) {
+ int csock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (csock < 0) {
+ perror("client socket");
+ return 5;
+ }
+ if (connect(csock, (struct sockaddr *)&listenaddr, listenaddr_len)) {
+ perror("connect");
+ return 6;
+ }
+
+ int packet_len = std::min((unsigned long)insize, sizeof(Packet));
+ sendto(csock, buf, packet_len, 0, (struct sockaddr *)&listenaddr,
+ listenaddr_len);
+ insize -= packet_len;
+ close(csock);
+ }
+
+ isoping_main(argc, argv, &dsessions, sock);
+ close(sock);
+ freeaddrinfo(res);
+}
diff --git a/cmds/isoping_fuzz/testcases/connection_established b/cmds/isoping_fuzz/testcases/connection_established
new file mode 100644
index 0000000..9842b91
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/connection_established
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/connection_packet b/cmds/isoping_fuzz/testcases/connection_packet
new file mode 100644
index 0000000..0bbbc38
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/connection_packet
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/handshake_packet b/cmds/isoping_fuzz/testcases/handshake_packet
new file mode 100644
index 0000000..52b3d76
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/handshake_packet
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/two_connections b/cmds/isoping_fuzz/testcases/two_connections
new file mode 100644
index 0000000..572d917
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/two_connections
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/unittest_min b/cmds/isoping_fuzz/testcases/unittest_min
new file mode 100644
index 0000000..b9ddc8d
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/unittest_min
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/zero_pkt b/cmds/isoping_fuzz/testcases/zero_pkt
new file mode 100644
index 0000000..19ee605
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/zero_pkt
Binary files differ
diff --git a/cmds/isoping_main.cc b/cmds/isoping_main.cc
index 521b5e1..78e323a 100644
--- a/cmds/isoping_main.cc
+++ b/cmds/isoping_main.cc
@@ -1,5 +1,22 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#include "isoping.h"
int main(int argc, char **argv) {
- return isoping_main(argc, argv);
+ Sessions sessions;
+ return isoping_main(argc, argv, &sessions, -1);
}
diff --git a/cmds/isoping_test.cc b/cmds/isoping_test.cc
index 9ab5cd9..acf96fd 100644
--- a/cmds/isoping_test.cc
+++ b/cmds/isoping_test.cc
@@ -501,13 +501,13 @@
Sessions s;
uint32_t usec_per_pkt = 100 * 1000;
- s.MaybeRotateCookieSecrets();
- // TODO(pmccurdy): Remove +1?
- c.NewSession(cbase + 1, usec_per_pkt, &listenaddr, listenaddr_len);
-
int is_server = 1;
int is_client = 0;
+ s.MaybeRotateCookieSecrets(sbase, is_server);
+ // TODO(pmccurdy): Remove +1?
+ c.NewSession(cbase + 1, usec_per_pkt, &listenaddr, listenaddr_len);
+
// Send the initial handshake packet.
Session &cSession = c.session_map.begin()->second;
uint32_t t = cSession.next_send - cbase;