From c037100747a1a224160cb12defb600ddfe1ba927 Mon Sep 17 00:00:00 2001 From: iphydf Date: Sat, 1 Oct 2016 00:26:52 +0100 Subject: Import the hstox SUT interface from hstox. We'll maintain it in the c-toxcore repo, where it belongs. --- testing/hstox/.gitignore | 8 ++ testing/hstox/Makefile | 97 ++++++++++++++ testing/hstox/binary_decode.c | 224 +++++++++++++++++++++++++++++++ testing/hstox/binary_encode.c | 252 +++++++++++++++++++++++++++++++++++ testing/hstox/byteswap.h | 6 + testing/hstox/driver.c | 244 +++++++++++++++++++++++++++++++++ testing/hstox/driver.h | 20 +++ testing/hstox/errors.h | 11 ++ testing/hstox/fuzz_main.c | 23 ++++ testing/hstox/methods.c | 147 ++++++++++++++++++++ testing/hstox/methods.h | 47 +++++++ testing/hstox/packet_kinds.c | 48 +++++++ testing/hstox/packet_kinds.h | 5 + testing/hstox/test-inputs/.gitignore | 1 + testing/hstox/test_main.c | 72 ++++++++++ testing/hstox/toxcore-sut.hs | 31 +++++ testing/hstox/util.c | 74 ++++++++++ testing/hstox/util.h | 27 ++++ 18 files changed, 1337 insertions(+) create mode 100644 testing/hstox/.gitignore create mode 100644 testing/hstox/Makefile create mode 100644 testing/hstox/binary_decode.c create mode 100644 testing/hstox/binary_encode.c create mode 100644 testing/hstox/byteswap.h create mode 100644 testing/hstox/driver.c create mode 100644 testing/hstox/driver.h create mode 100644 testing/hstox/errors.h create mode 100644 testing/hstox/fuzz_main.c create mode 100644 testing/hstox/methods.c create mode 100644 testing/hstox/methods.h create mode 100644 testing/hstox/packet_kinds.c create mode 100644 testing/hstox/packet_kinds.h create mode 100644 testing/hstox/test-inputs/.gitignore create mode 100644 testing/hstox/test_main.c create mode 100644 testing/hstox/toxcore-sut.hs create mode 100644 testing/hstox/util.c create mode 100644 testing/hstox/util.h (limited to 'testing') diff --git a/testing/hstox/.gitignore b/testing/hstox/.gitignore new file mode 100644 index 00000000..6fe429cf --- /dev/null +++ b/testing/hstox/.gitignore @@ -0,0 +1,8 @@ +/*.dSYM +/*_main-* +/test-findings +!/Makefile +*.gcno +*.gcda +*.hi +*.o diff --git a/testing/hstox/Makefile b/testing/hstox/Makefile new file mode 100644 index 00000000..ec02719c --- /dev/null +++ b/testing/hstox/Makefile @@ -0,0 +1,97 @@ +# Don't bind to a CPU core. afl-fuzz doesn't release them again. +export AFL_NO_AFFINITY := 1 + +CFLAGS := \ + -std=gnu99 \ + -Wall \ + -g3 \ + -DCONFIGURED=1 \ + -Imsgpack-c/include \ + -I../../toxcore \ + +LDFLAGS := -pthread -lsodium +ifeq ($(shell echo 'int main(){}' | $(CC) -lrt -xc -o /dev/null - && echo true),true) +LDFLAGS += -lrt +endif + +SOURCES := $(filter-out %_main.c,$(wildcard *.c msgpack-c/src/*.c ../../toxcore/*.c)) +HEADERS := $(shell find . -name "*.h") + +ifeq ($(wildcard test-findings/master/fuzzer_stats),) +TEST_INPUTS := test-inputs +else +TEST_INPUTS := - +endif + +# All the possible test flavours. +PROGRAMS := \ + test_main-cov \ + test_main-cov-asan \ + test_main-vanilla \ + test_main-vanilla-asan + +ifneq ($(shell which afl-clang),) +AFL_CLANG := afl-clang +endif + +ifneq ($(shell which afl-clang-fast),) +AFL_CLANG := afl-clang-fast +endif + +ifneq ($(AFL_CLANG),) +PROGRAMS += \ + fuzz_main-afl \ + fuzz_main-afl-asan +endif + +all: $(PROGRAMS) + +check: test_main-sut + ./$< ./$< + +master: fuzz_main-afl test-findings + afl-fuzz -i$(TEST_INPUTS) -o test-findings -M master ./$< + +slave%: fuzz_main-afl test-findings + afl-fuzz -i test-inputs -o test-findings -S slave$* ./$< + +%-afl: %.c $(SOURCES) $(HEADERS) + $(AFL_CLANG) $(CFLAGS) -O3 $(filter %.c,$^) $(LDFLAGS) -o $@ + +%-afl-asan: %.c $(SOURCES) $(HEADERS) + $(AFL_CLANG) $(CFLAGS) -O3 -fsanitize=address $(filter %.c,$^) $(LDFLAGS) -o $@ + +%-cov: %.c $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) -fprofile-arcs -ftest-coverage $(filter %.c,$^) $(LDFLAGS) -o $@ + +%-cov-asan: %.c $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) -fprofile-arcs -ftest-coverage -fsanitize=address $(filter %.c,$^) $(LDFLAGS) -o $@ + +%-vanilla: %.c $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) $(filter %.c,$^) $(LDFLAGS) -o $@ + +%-vanilla-asan: %.c $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) -fsanitize=address $(filter %.c,$^) $(LDFLAGS) -o $@ + +%-sut: toxcore-sut.hs %.c $(SOURCES) $(HEADERS) + ghc -package hstox $< \ + $(filter %.c,$^) \ + $(foreach f,$(CFLAGS),-optc $f) \ + $(foreach f,$(LDFLAGS),-optl $f) \ + -o $@ + +ifneq ($(wildcard /Volumes),) +test-findings: /Volumes/RAM\ Disk + mkdir -p "$ + +METHOD(bin, Binary_decode, CipherText) +{ + uint64_t length; + uint64_t tmp; + + SUCCESS { + memcpy(&tmp, args.ptr, sizeof(uint64_t)); + length = be64toh(tmp); + + if (args.size >= sizeof(uint64_t) && args.size == length + sizeof(uint64_t)) + { + msgpack_pack_bin(res, args.size - sizeof(uint64_t)); + msgpack_pack_bin_body(res, args.ptr + sizeof(uint64_t), args.size - sizeof(uint64_t)); + } else { + msgpack_pack_nil(res); + } + } + return 0; +} + +METHOD(bin, Binary_decode, DhtPacket) +{ + return pending; +} + +METHOD(bin, Binary_decode, HostAddress) +{ + return pending; +} + +METHOD(bin, Binary_decode, Word64) +{ + return pending; +} + +METHOD(bin, Binary_decode, Key_PublicKey) +{ + return pending; +} + +METHOD(bin, Binary_decode, KeyPair) +{ + SUCCESS { + if (args.size != 64) + { + msgpack_pack_nil(res); + } else { + msgpack_pack_array(res, 2); + msgpack_pack_bin(res, 32); + msgpack_pack_bin_body(res, args.ptr, 32); + msgpack_pack_bin(res, 32); + msgpack_pack_bin_body(res, args.ptr + 32, 32); + } + } + + return 0; +} + +METHOD(bin, Binary_decode, NodeInfo) +{ + uint16_t data_processed; + Node_format node; + int len = unpack_nodes(&node, 1, &data_processed, (uint8_t const *)args.ptr, args.size, 1); + + bool ip6_node = node.ip_port.ip.family == AF_INET6 || node.ip_port.ip.family == TCP_INET6; + bool tcp = node.ip_port.ip.family == TCP_INET || node.ip_port.ip.family == TCP_INET6; + + uint16_t port = ntohs(node.ip_port.port); + uint32_t ip4 = ntohl(node.ip_port.ip.ip4.uint32); + uint32_t ip6_0 = ntohl(node.ip_port.ip.ip6.uint32[0]); + uint32_t ip6_1 = ntohl(node.ip_port.ip.ip6.uint32[1]); + uint32_t ip6_2 = ntohl(node.ip_port.ip.ip6.uint32[2]); + uint32_t ip6_3 = ntohl(node.ip_port.ip.ip6.uint32[3]); + + SUCCESS { + if (len > 0 && data_processed > 0 && data_processed == args.size) + { + msgpack_pack_array(res, 3); + msgpack_pack_uint8(res, tcp); + msgpack_pack_array(res, 2); + msgpack_pack_array(res, 2); + msgpack_pack_uint8(res, ip6_node); + + if (ip6_node) { + msgpack_pack_array(res, 4); + msgpack_pack_uint32(res, ip6_0); + msgpack_pack_uint32(res, ip6_1); + msgpack_pack_uint32(res, ip6_2); + msgpack_pack_uint32(res, ip6_3); + } else { + msgpack_pack_uint32(res, ip4); + } + + msgpack_pack_uint16(res, port); + msgpack_pack_bin(res, crypto_box_PUBLICKEYBYTES); + msgpack_pack_bin_body(res, &node.public_key, crypto_box_PUBLICKEYBYTES); + } else { + msgpack_pack_nil(res); + } + } + + return 0; +} + +METHOD(bin, Binary_decode, NodesRequest) +{ + return pending; +} + +METHOD(bin, Binary_decode, NodesResponse) +{ + return pending; +} + +METHOD(bin, Binary_decode, Packet_Word64) +{ + return pending; +} + +METHOD(bin, Binary_decode, PacketKind) +{ + SUCCESS { + if (args.size != 1) + { + msgpack_pack_nil(res); + } else { + uint8_t kind = args.ptr[0]; + size_t i; + + for (i = 0; i < sizeof packet_kinds / sizeof *packet_kinds; i++) + { + if (packet_kinds[i] == kind) { + msgpack_pack_fix_uint8(res, i); + return 0; + } + } + + // Packet kind not found => error. + msgpack_pack_nil(res); + } + } + return 0; +} + +METHOD(bin, Binary_decode, PingPacket) +{ + return pending; +} + +METHOD(bin, Binary_decode, PlainText) +{ + return pending; +} + +METHOD(bin, Binary_decode, PortNumber) +{ + SUCCESS { + if (args.size == 2) + { + uint16_t tmp; + memcpy(&tmp, args.ptr, 2); + uint16_t port = ntohs(tmp); + msgpack_pack_uint16(res, port); + } else { + msgpack_pack_nil(res); + } + } + + return 0; +} + +METHOD(bin, Binary_decode, RpcPacket_Word64) +{ + return pending; +} + +METHOD(bin, Binary_decode, SocketAddress) +{ + return pending; +} + +METHOD(bin, Binary_decode, TransportProtocol) +{ + return pending; +} + +METHOD(array, Binary, decode) +{ + CHECK_SIZE(args, 2); + CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_STR); + CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_BIN); + + msgpack_object_str type = args.ptr[0].via.str; +#define DISPATCH(TYPE) \ + if (type.size == sizeof #TYPE - 1 && method_cmp(type.ptr, #TYPE, type.size) == 0) \ + return Binary_decode_##TYPE(args.ptr[1].via.bin, res) + DISPATCH(CipherText); + DISPATCH(DhtPacket); + DISPATCH(HostAddress); + DISPATCH(Word64); + DISPATCH(Key_PublicKey); + DISPATCH(KeyPair); + DISPATCH(NodeInfo); + DISPATCH(NodesRequest); + DISPATCH(NodesResponse); + DISPATCH(Packet_Word64); + DISPATCH(PacketKind); + DISPATCH(PingPacket); + DISPATCH(PlainText); + DISPATCH(PortNumber); + DISPATCH(RpcPacket_Word64); + DISPATCH(SocketAddress); + DISPATCH(TransportProtocol); +#undef DISPATCH + + return unimplemented; +} diff --git a/testing/hstox/binary_encode.c b/testing/hstox/binary_encode.c new file mode 100644 index 00000000..a39533c0 --- /dev/null +++ b/testing/hstox/binary_encode.c @@ -0,0 +1,252 @@ +#include "methods.h" + +#include "byteswap.h" +#include "packet_kinds.h" + +#include + +METHOD(bin, Binary_encode, CipherText) +{ + uint64_t length = be64toh(args.size); + + SUCCESS { + msgpack_pack_bin(res, sizeof(uint64_t) + args.size); + msgpack_pack_bin_body(res, &length, sizeof(uint64_t)); + msgpack_pack_bin_body(res, args.ptr, args.size); + } + return 0; +} + +METHOD(array, Binary_encode, DhtPacket) +{ + return pending; +} + +METHOD(array, Binary_encode, HostAddress) +{ + return pending; +} + +METHOD(u64, Binary_encode, Word64) +{ + return pending; +} + +METHOD(bin, Binary_encode, Key_PublicKey) +{ + return pending; +} + +METHOD(array, Binary_encode, KeyPair) +{ + CHECK_SIZE(args, 2); + CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_BIN); + CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_BIN); + + msgpack_object_bin secret_key = args.ptr[0].via.bin; + msgpack_object_bin public_key = args.ptr[1].via.bin; + + CHECK_SIZE(secret_key, 32); + CHECK_SIZE(public_key, 32); + + SUCCESS { + uint8_t data[64]; + memcpy(data, secret_key.ptr, 32); + memcpy(data + 32, public_key.ptr, 32); + msgpack_pack_bin(res, 64); + msgpack_pack_bin_body(res, data, 64); + } + + return 0; +} + +#define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES) +METHOD(array, Binary_encode, NodeInfo) +{ + CHECK_SIZE(args, 3); + + CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_POSITIVE_INTEGER); + uint64_t protocol = args.ptr[0].via.u64; + + CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_ARRAY); + msgpack_object_array address = args.ptr[1].via.array; + + CHECK_SIZE(address, 2); + CHECK_TYPE(address.ptr[0], MSGPACK_OBJECT_ARRAY); + msgpack_object_array host_address = address.ptr[0].via.array; + + CHECK_TYPE(address.ptr[1], MSGPACK_OBJECT_POSITIVE_INTEGER); + uint64_t port_number = address.ptr[1].via.u64; + + CHECK_SIZE(host_address, 2); + CHECK_TYPE(host_address.ptr[0], MSGPACK_OBJECT_POSITIVE_INTEGER); + uint64_t address_family = host_address.ptr[0].via.u64; + + CHECK_TYPE(args.ptr[2], MSGPACK_OBJECT_BIN); + msgpack_object_bin public_key = args.ptr[2].via.bin; + + CHECK_SIZE(public_key, crypto_box_PUBLICKEYBYTES); + + IP_Port ipp; + ipp.port = htons(port_number); + + switch (address_family) { + case 0: { + /* IPv4*/ + if (protocol == 1) { + ipp.ip.family = TCP_INET; + } else { + ipp.ip.family = AF_INET; + } + + CHECK_TYPE(host_address.ptr[1], MSGPACK_OBJECT_POSITIVE_INTEGER); + uint64_t addr = host_address.ptr[1].via.u64; + + ipp.ip.ip4.uint32 = htonl(addr); + break; + } + + case 1: { + /* IPv6 */ + if (protocol == 1) { + ipp.ip.family = TCP_INET6; + } else { + ipp.ip.family = AF_INET6; + } + + CHECK_TYPE(host_address.ptr[1], MSGPACK_OBJECT_ARRAY); + msgpack_object_array addr = host_address.ptr[1].via.array; + + int i; + + for (i = 0; i < 4; ++i) { + CHECK_TYPE(addr.ptr[i], MSGPACK_OBJECT_POSITIVE_INTEGER); + uint64_t component = addr.ptr[i].via.u64; + ipp.ip.ip6.uint32[i] = htonl(component); + } + + break; + } + } + + Node_format node; + node.ip_port = ipp; + memcpy(&node.public_key, public_key.ptr, crypto_box_PUBLICKEYBYTES); + + /* We assume IP6 because it's bigger */ + uint8_t packed_node[PACKED_NODE_SIZE_IP6]; + + int len = pack_nodes(packed_node, sizeof packed_node, &node, 1); + + if (len < 0) { + return failure; + } + + SUCCESS { + msgpack_pack_bin(res, len); + msgpack_pack_bin_body(res, packed_node, len); + } + return 0; +} + +METHOD(bin, Binary_encode, NodesRequest) +{ + return pending; +} + +METHOD(array, Binary_encode, NodesResponse) +{ + return pending; +} + +METHOD(array, Binary_encode, Packet_Word64) +{ + return pending; +} + +METHOD(u64, Binary_encode, PacketKind) +{ + CHECK(args < sizeof packet_kinds / sizeof * packet_kinds); + + SUCCESS { + uint8_t data[] = {packet_kinds[args]}; + msgpack_pack_bin(res, sizeof data); + msgpack_pack_bin_body(res, data, sizeof data); + } + return 0; +} + +METHOD(u64, Binary_encode, PingPacket) +{ + return pending; +} + +METHOD(bin, Binary_encode, PlainText) +{ + return pending; +} + +METHOD(u64, Binary_encode, PortNumber) +{ + SUCCESS { + if (args <= UINT16_MAX) + { + uint16_t port = ntohs(args); + msgpack_pack_bin(res, 2); + msgpack_pack_bin_body(res, &port, 2); + } else { + msgpack_pack_nil(res); + } + } + return 0; +} + +METHOD(array, Binary_encode, RpcPacket_Word64) +{ + return pending; +} + +METHOD(array, Binary_encode, SocketAddress) +{ + return pending; +} + +METHOD(u64, Binary_encode, TransportProtocol) +{ + return pending; +} + +METHOD(array, Binary, encode) +{ + CHECK_SIZE(args, 2); + CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_STR); + + msgpack_object_str type = args.ptr[0].via.str; +#define DISPATCH(TYPE, UTYPE, LTYPE) \ + do { \ + if (type.size == sizeof #TYPE - 1 && method_cmp(type.ptr, #TYPE, type.size) == 0) { \ + CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_##UTYPE); \ + return Binary_encode_##TYPE(args.ptr[1].via.LTYPE, res); \ + } \ + } while (0) + DISPATCH(CipherText, BIN, bin); + DISPATCH(DhtPacket, ARRAY, array); + DISPATCH(HostAddress, ARRAY, array); + DISPATCH(Word64, POSITIVE_INTEGER, u64); + DISPATCH(Key_PublicKey, BIN, bin); + DISPATCH(KeyPair, ARRAY, array); + DISPATCH(NodeInfo, ARRAY, array); + DISPATCH(NodesRequest, BIN, bin); + DISPATCH(NodesResponse, ARRAY, array); + DISPATCH(Packet_Word64, ARRAY, array); + DISPATCH(PacketKind, POSITIVE_INTEGER, u64); + DISPATCH(PingPacket, POSITIVE_INTEGER, u64); + DISPATCH(PlainText, BIN, bin); + DISPATCH(PortNumber, POSITIVE_INTEGER, u64); + DISPATCH(RpcPacket_Word64, ARRAY, array); + DISPATCH(SocketAddress, ARRAY, array); + DISPATCH(TransportProtocol, POSITIVE_INTEGER, u64); +#undef DISPATCH + + return unimplemented; +} diff --git a/testing/hstox/byteswap.h b/testing/hstox/byteswap.h new file mode 100644 index 00000000..b5a58f66 --- /dev/null +++ b/testing/hstox/byteswap.h @@ -0,0 +1,6 @@ +#ifdef __APPLE__ +#include + +#define htobe64(x) OSSwapHostToBigInt64(x) +#define be64toh(x) OSSwapBigToHostInt64(x) +#endif diff --git a/testing/hstox/driver.c b/testing/hstox/driver.c new file mode 100644 index 00000000..81773190 --- /dev/null +++ b/testing/hstox/driver.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "driver.h" +#include "errors.h" +#include "methods.h" +#include "util.h" + +#include + +static void handle_interrupt(int signum) +{ + printf("Caught signal %d; exiting cleanly.\n", signum); + exit(0); +} + +static int protocol_error(msgpack_packer *pk, char const *fmt, ...) +{ + msgpack_pack_array(pk, 4); // 4 elements in the array + msgpack_pack_uint8(pk, 1); // 1. type = response + // 2. We don't know the msgid, because the packet we received is not a valid + // msgpack-rpc packet. + msgpack_pack_uint64(pk, 0); + + // 3. Error message. + va_list ap; + va_start(ap, fmt); + int res = msgpack_pack_vstringf(pk, fmt, ap); + va_end(ap); + + // 4. No success result. + msgpack_pack_array(pk, 0); + + return res; +} + +static bool type_check(msgpack_packer *pk, msgpack_object req, int index, + msgpack_object_type type) +{ + if (req.via.array.ptr[index].type != type) { + protocol_error(pk, "element %d should be %s, but is %s", index, type_name(type), + type_name(req.via.array.ptr[index].type)); + return false; + } + + return true; +} + +static int write_sample_input(msgpack_object req) +{ + static unsigned int n; + + char filename[256]; + msgpack_object_str name = req.via.array.ptr[2].via.str; + snprintf(filename, sizeof filename - name.size, "test-inputs/%04u-", n++); + + assert(sizeof filename - strlen(filename) > name.size + 4); + memcpy(filename + strlen(filename) + name.size, ".mp", 4); + memcpy(filename + strlen(filename), name.ptr, name.size); + + int fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + + if (fd < 0) + // If we can't open the sample file, we just don't write it. + { + return E_OK; + } + + check_return(E_WRITE, ftruncate(fd, 0)); + + msgpack_sbuffer sbuf __attribute__((__cleanup__(msgpack_sbuffer_destroy))); + msgpack_sbuffer_init(&sbuf); + + msgpack_packer pk; + msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write); + + msgpack_pack_object(&pk, req); + + check_return(E_WRITE, write(fd, sbuf.data, sbuf.size)); + + return E_OK; +} + +static int handle_request(struct settings cfg, int write_fd, msgpack_object req) +{ + msgpack_sbuffer sbuf __attribute__((__cleanup__(msgpack_sbuffer_destroy))); /* buffer */ + msgpack_sbuffer_init(&sbuf); /* initialize buffer */ + + msgpack_packer pk; /* packer */ + msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write); /* initialize packer */ + + if (req.type != MSGPACK_OBJECT_ARRAY) { + protocol_error(&pk, "expected array, but got %s", type_name(req.type)); + } else if (req.via.array.size != 4) { + protocol_error(&pk, "array length should be 4, but is %d", req.via.array.size); + } else if (type_check(&pk, req, 0, MSGPACK_OBJECT_POSITIVE_INTEGER) && + type_check(&pk, req, 1, MSGPACK_OBJECT_POSITIVE_INTEGER) && + type_check(&pk, req, 2, MSGPACK_OBJECT_STR) && + type_check(&pk, req, 3, MSGPACK_OBJECT_ARRAY)) { + if (cfg.collect_samples) { + propagate(write_sample_input(req)); + } + + uint64_t msgid = req.via.array.ptr[1].via.u64; + msgpack_object_str name = req.via.array.ptr[2].via.str; + msgpack_object_array args = req.via.array.ptr[3].via.array; + + msgpack_pack_array(&pk, 4); // 4 elements in the array + msgpack_pack_uint8(&pk, 1); // 1. type = response + msgpack_pack_uint64(&pk, msgid); // 2. msgid + + if (name.size == (sizeof "rpc.capabilities") - 1 && + memcmp(name.ptr, "rpc.capabilities", name.size) == 0) { + // 3. Error. + msgpack_pack_string(&pk, "Capabilities negiotiation not implemented"); + // 4. No result. + msgpack_pack_nil(&pk); + } else { + // if error is null, this writes 3. no error, and 4. result + char const *error = + call_method(name, args, &pk); + + if (error) { + if (cfg.debug) { + printf("Error '%s' in request: ", error); + msgpack_object_print(stdout, req); + printf("\n"); + } + + msgpack_pack_string(&pk, error); + msgpack_pack_array(&pk, 0); + } + } + } + + check_return(E_WRITE, write(write_fd, sbuf.data, sbuf.size)); + + return E_OK; +} + +int communicate(struct settings cfg, int read_fd, int write_fd) +{ + msgpack_unpacker unp __attribute__((__cleanup__(msgpack_unpacker_destroy))); + msgpack_unpacker_init(&unp, 128); + + while (true) { + char buf[64]; + int size = check_return(E_READ, read(read_fd, buf, sizeof buf)); + + if (size == 0) { + break; + } + + if (msgpack_unpacker_buffer_capacity(&unp) < size && + !msgpack_unpacker_reserve_buffer(&unp, size)) { + return E_NOMEM; + } + + memcpy(msgpack_unpacker_buffer(&unp), buf, size); + msgpack_unpacker_buffer_consumed(&unp, size); + + msgpack_unpacked req __attribute__((__cleanup__(msgpack_unpacked_destroy))); + msgpack_unpacked_init(&req); + + switch (msgpack_unpacker_next(&unp, &req)) { + case MSGPACK_UNPACK_SUCCESS: + propagate(handle_request(cfg, write_fd, req.data)); + break; + + case MSGPACK_UNPACK_EXTRA_BYTES: + printf("EXTRA_BYTES\n"); + break; + + case MSGPACK_UNPACK_CONTINUE: + break; + + case MSGPACK_UNPACK_PARSE_ERROR: + return E_PARSE; + + case MSGPACK_UNPACK_NOMEM_ERROR: + return E_NOMEM; + } + } + + return E_OK; +} + +static int closep(int *fd) +{ + return close(*fd); +} + +static int run_tests(struct settings cfg, int port) +{ + int listen_fd __attribute__((__cleanup__(closep))) = 0; + listen_fd = check_return(E_SOCKET, socket(AF_INET, SOCK_STREAM, 0)); + check_return(E_SOCKET, setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &(int) { + 1 + }, sizeof(int))); + + struct sockaddr_in servaddr; + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htons(INADDR_ANY); + servaddr.sin_port = htons(port); + + check_return(E_BIND, bind(listen_fd, (struct sockaddr *)&servaddr, sizeof servaddr)); + check_return(E_LISTEN, listen(listen_fd, 10)); + + while (true) { + int comm_fd __attribute__((__cleanup__(closep))) = 0; + comm_fd = check_return(E_ACCEPT, accept(listen_fd, NULL, NULL)); + propagate(communicate(cfg, comm_fd, comm_fd)); + } + + return E_OK; +} + +uint32_t network_main(struct settings cfg, uint16_t port, unsigned int timeout) +{ + signal(SIGALRM, handle_interrupt); + signal(SIGINT, handle_interrupt); + check_return(E_SODIUM, sodium_init()); + + // Kill the process after `timeout` seconds so we don't get lingering + // processes bound to the test port when something goes wrong with a test run. + alarm(timeout); + + int result = run_tests(cfg, port); + + if (result == E_OK) { + return E_OK; + } + + return result | (errno << 8); +} diff --git a/testing/hstox/driver.h b/testing/hstox/driver.h new file mode 100644 index 00000000..219df8a1 --- /dev/null +++ b/testing/hstox/driver.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +// Settings for the test runner. +struct settings { + // Print the msgpack object on test failure. + bool debug; + // Write test sample files into test-inputs/. These files, one per test + // method, are used to seed the fuzzer. + bool collect_samples; +}; + +// Main loop communicating via read/write file descriptors. The two fds can be +// the same in case of a network socket. +int communicate(struct settings cfg, int read_fd, int write_fd); + +// Open a TCP socket on the given port and start communicate(). +uint32_t network_main(struct settings cfg, uint16_t port, unsigned int timeout); diff --git a/testing/hstox/errors.h b/testing/hstox/errors.h new file mode 100644 index 00000000..42bf864a --- /dev/null +++ b/testing/hstox/errors.h @@ -0,0 +1,11 @@ +#define E_OK 0 +#define E_NOMEM 1 +#define E_SOCKET 2 +#define E_BIND 3 +#define E_LISTEN 4 +#define E_ACCEPT 5 +#define E_PARSE 6 +#define E_OPEN 7 +#define E_READ 8 +#define E_WRITE 9 +#define E_SODIUM 10 diff --git a/testing/hstox/fuzz_main.c b/testing/hstox/fuzz_main.c new file mode 100644 index 00000000..ff66a166 --- /dev/null +++ b/testing/hstox/fuzz_main.c @@ -0,0 +1,23 @@ +#include "driver.h" + +#include + +// The number of afl iterations before terminating the process and starting a +// new one. +// See https://github.com/mcarpenter/afl/blob/master/llvm_mode/README.llvm. +#define ITERATIONS 1000 + +#ifdef __GLASGOW_HASKELL__ +#define main fuzz_main +#endif +int main(int argc, char **argv) +{ + struct settings cfg = {false, false}; +#ifdef __AFL_LOOP + + while (__AFL_LOOP(ITERATIONS)) +#endif + communicate(cfg, STDIN_FILENO, STDOUT_FILENO); + + return 0; +} diff --git a/testing/hstox/methods.c b/testing/hstox/methods.c new file mode 100644 index 00000000..08ad50fa --- /dev/null +++ b/testing/hstox/methods.c @@ -0,0 +1,147 @@ +#include "methods.h" + +#include "util.h" + +#include +#include + +char const *const failure = "Failure"; +char const *const pending = "Pending"; +char const *const unimplemented = "Unimplemented"; + +METHOD(array, Box, encrypt) +{ + return pending; +} + +METHOD(array, Box, decrypt) +{ + return pending; +} + +METHOD(array, CombinedKey, precompute) +{ + return pending; +} + +METHOD(array, KeyPair, newKeyPair) +{ + uint8_t key1[crypto_box_PUBLICKEYBYTES]; + uint8_t key2[crypto_box_SECRETKEYBYTES]; + crypto_box_keypair(key1, key2); + + SUCCESS { + // init array + msgpack_pack_array(res, 2); + msgpack_pack_bin(res, crypto_box_PUBLICKEYBYTES); + msgpack_pack_bin_body(res, key1, crypto_box_PUBLICKEYBYTES); + + msgpack_pack_bin(res, crypto_box_SECRETKEYBYTES); + msgpack_pack_bin_body(res, key2, crypto_box_SECRETKEYBYTES); + } + return 0; +} + +METHOD(array, KeyPair, fromSecretKey) +{ + CHECK_SIZE(args, 1); + CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_BIN); + CHECK_SIZE(args.ptr[0].via.bin, crypto_box_SECRETKEYBYTES); + + Net_Crypto c; + uint8_t secret_key[crypto_box_SECRETKEYBYTES]; + memcpy(secret_key, args.ptr[0].via.bin.ptr, crypto_box_SECRETKEYBYTES); + load_secret_key(&c, secret_key); + + SUCCESS { + msgpack_pack_array(res, 2); + + msgpack_pack_bin(res, crypto_box_PUBLICKEYBYTES); + msgpack_pack_bin_body(res, c.self_secret_key, crypto_box_PUBLICKEYBYTES); + msgpack_pack_bin(res, crypto_box_SECRETKEYBYTES); + msgpack_pack_bin_body(res, c.self_public_key, crypto_box_SECRETKEYBYTES); + } + return 0; +} + +METHOD(array, Nonce, newNonce) +{ + uint8_t nonce[24] = {0}; + new_nonce(nonce); + + SUCCESS { + msgpack_pack_bin(res, sizeof nonce); + msgpack_pack_bin_body(res, nonce, sizeof nonce); + } + + return 0; +} + +METHOD(array, Nonce, increment) +{ + CHECK_SIZE(args, 1); + CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_BIN); + CHECK_SIZE(args.ptr[0].via.bin, 24); + + uint8_t nonce[24]; + memcpy(nonce, args.ptr[0].via.bin.ptr, 24); + increment_nonce(nonce); + + SUCCESS { + msgpack_pack_bin(res, sizeof nonce); + msgpack_pack_bin_body(res, nonce, sizeof nonce); + } + + return 0; +} + +METHOD(array, rpc, capabilities) +{ + return pending; +} + +char const *call_method(msgpack_object_str name, msgpack_object_array args, msgpack_packer *res) +{ +#define DISPATCH(SERVICE, NAME) \ + if (name.size == sizeof #SERVICE "." #NAME - 1 && \ + memcmp(name.ptr, #SERVICE "." #NAME, name.size) == 0) \ + return SERVICE##_##NAME(args, res) + DISPATCH(Binary, decode); + DISPATCH(Binary, encode); + DISPATCH(Box, decrypt); + DISPATCH(Box, encrypt); + DISPATCH(CombinedKey, precompute); + DISPATCH(KeyPair, fromSecretKey); + DISPATCH(KeyPair, newKeyPair); + DISPATCH(Nonce, increment); + DISPATCH(Nonce, newNonce); +#undef DISPATCH + + // Default action: "Unimplemented" exception. New tests should be added here + // returning "Pending" until they are properly implemented. + return unimplemented; +} + +int method_cmp(char const *ptr, char const *expected, size_t max_size) +{ + char *transformed = malloc(max_size); + + if (transformed == NULL) { + return memcmp(ptr, expected, max_size); + } + + memcpy(transformed, ptr, max_size); + size_t i; + + for (i = 0; i < max_size; i++) { + switch (transformed[i]) { + case '(': + case ')': + case ' ': + transformed[i] = '_'; + break; + } + } + + return memcmp(transformed, expected, max_size); +} diff --git a/testing/hstox/methods.h b/testing/hstox/methods.h new file mode 100644 index 00000000..1a7f3059 --- /dev/null +++ b/testing/hstox/methods.h @@ -0,0 +1,47 @@ +#pragma once + +#include "util.h" + +#include + +#define CHECK(COND) \ + do { \ + if (!(COND)) \ + return #COND; \ + } while (0) + +#define CHECK_SIZE(ARG, SIZE) \ + do { \ + if ((ARG).size != (SIZE)) \ + return ssprintf("%s:%d: Size of `" #ARG "' expected to be %zu, but was %zu", __FILE__, \ + __LINE__, SIZE, (ARG).size); \ + } while (0) + +#define CHECK_TYPE(ARG, TYPE) \ + do { \ + if ((ARG).type != (TYPE)) \ + return ssprintf("%s:%d: Type of `" #ARG "' expected to be %s, but was %s", __FILE__, \ + __LINE__, type_name(TYPE), type_name((ARG).type)); \ + } while (0) + +#define SUCCESS \ + msgpack_pack_array(res, 0); \ + if (true) + +#define METHOD(TYPE, SERVICE, NAME) \ + char const *SERVICE##_##NAME(msgpack_object_##TYPE args, msgpack_packer *res) + +// These are not defined by msgpack.h, but we need them for uniformity in the +// METHOD macro. +typedef int64_t msgpack_object_i64; +typedef uint64_t msgpack_object_u64; + +METHOD(array, Binary, decode); +METHOD(array, Binary, encode); + +char const *call_method(msgpack_object_str name, msgpack_object_array args, msgpack_packer *res); +int method_cmp(char const *ptr, char const *expected, size_t max_size); + +extern char const *const failure; +extern char const *const pending; +extern char const *const unimplemented; diff --git a/testing/hstox/packet_kinds.c b/testing/hstox/packet_kinds.c new file mode 100644 index 00000000..314c814b --- /dev/null +++ b/testing/hstox/packet_kinds.c @@ -0,0 +1,48 @@ +#include "packet_kinds.h" + +#include + +uint8_t const packet_kinds[21] = { + // = PingRequest -- 0x00: Ping request + NET_PACKET_PING_REQUEST, + // | PingResponse -- 0x01: Ping response + NET_PACKET_PING_RESPONSE, + // | NodesRequest -- 0x02: Nodes request + NET_PACKET_GET_NODES, + // | NodesResponse -- 0x04: Nodes response + NET_PACKET_SEND_NODES_IPV6, + // | CookieRequest -- 0x18: Cookie request + NET_PACKET_COOKIE_REQUEST, + // | CookieResponse -- 0x19: Cookie response + NET_PACKET_COOKIE_RESPONSE, + // | CryptoHandshake -- 0x1a: Crypto handshake + NET_PACKET_CRYPTO_HS, + // | CryptoData -- 0x1b: Crypto data + NET_PACKET_CRYPTO_DATA, + // | Crypto -- 0x20: Encrypted data + NET_PACKET_CRYPTO, + // | LanDiscovery -- 0x21: LAN discovery + NET_PACKET_LAN_DISCOVERY, + // | OnionRequest0 -- 0x80: Initial onion request + NET_PACKET_ONION_SEND_INITIAL, + // | OnionRequest1 -- 0x81: First level wrapped onion request + NET_PACKET_ONION_SEND_1, + // | OnionRequest2 -- 0x82: Second level wrapped onion request + NET_PACKET_ONION_SEND_2, + // | AnnounceRequest -- 0x83: Announce request + NET_PACKET_ANNOUNCE_REQUEST, + // | AnnounceResponse -- 0x84: Announce response + NET_PACKET_ANNOUNCE_RESPONSE, + // | OnionDataRequest -- 0x85: Onion data request + NET_PACKET_ONION_DATA_REQUEST, + // | OnionDataResponse -- 0x86: Onion data response + NET_PACKET_ONION_DATA_RESPONSE, + // | OnionResponse3 -- 0x8c: Third level wrapped onion response + NET_PACKET_ONION_RECV_3, + // | OnionResponse2 -- 0x8d: Second level wrapped onion response + NET_PACKET_ONION_RECV_2, + // | OnionResponse1 -- 0x8e: First level wrapped onion response + NET_PACKET_ONION_RECV_1, + // | BootstrapInfo -- 0xf0: Bootstrap node info request and response + BOOTSTRAP_INFO_PACKET_ID, +}; diff --git a/testing/hstox/packet_kinds.h b/testing/hstox/packet_kinds.h new file mode 100644 index 00000000..ab019ab1 --- /dev/null +++ b/testing/hstox/packet_kinds.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern uint8_t const packet_kinds[21]; diff --git a/testing/hstox/test-inputs/.gitignore b/testing/hstox/test-inputs/.gitignore new file mode 100644 index 00000000..5eb3d40d --- /dev/null +++ b/testing/hstox/test-inputs/.gitignore @@ -0,0 +1 @@ +/?*.* diff --git a/testing/hstox/test_main.c b/testing/hstox/test_main.c new file mode 100644 index 00000000..7ac18da0 --- /dev/null +++ b/testing/hstox/test_main.c @@ -0,0 +1,72 @@ +#include "driver.h" +#include "errors.h" + +#include +#include + +// The msgpack-rpc test port expected by the test runner. +#define PORT 1234 + +// Timeout in seconds after which the driver shuts down. Currently, a complete +// test run takes about 7 seconds. The timeout of 2 minutes is a guess so it +// keeps working for a while, even on a very slow computer. +#define TIMEOUT 120 + +static char const *error_desc(int code) +{ + switch (code) { + case E_OK: + return "Success"; + + case E_NOMEM: + return "Error: Out of memory"; + + case E_SOCKET: + return "Error: socket creation failed"; + + case E_BIND: + return "Error: bind failed"; + + case E_LISTEN: + return "Error: listen failed"; + + case E_ACCEPT: + return "Error: accept failed"; + + case E_PARSE: + return "Error: unable to parse msgpack input"; + + case E_OPEN: + return "Error: open failed"; + + case E_READ: + return "Error: read failed"; + + case E_WRITE: + return "Error: write failed"; + + case E_SODIUM: + return "Error: libsodium initialisation failed"; + } + + return "Unknown error code"; +} + +#ifdef __GLASGOW_HASKELL__ +#define main test_main +#endif +int main(void) +{ + struct settings cfg = {true, true}; + uint32_t result = network_main(cfg, PORT, TIMEOUT); + int line = result >> 16; + int error = (result >> 8) & 0xff; + int code = result & 0xff; + + if (code != E_OK) { + printf("%s, errno=%d, line=%d\n", error_desc(code), error, line); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/testing/hstox/toxcore-sut.hs b/testing/hstox/toxcore-sut.hs new file mode 100644 index 00000000..73bdb702 --- /dev/null +++ b/testing/hstox/toxcore-sut.hs @@ -0,0 +1,31 @@ +module Main (main) where + +import Control.Concurrent (threadDelay) +import System.Environment (getArgs, withArgs) +import System.Process (createProcess, proc, terminateProcess) + +import Network.Tox.Testing (serve) +import qualified ToxTestSuite + + +foreign import ccall test_main :: IO () + + +main :: IO () +main = do + args <- getArgs + case args of + ["--sut"] -> test_main + self : testArgs -> do + -- Start a toxcore SUT (System Under Test) process that will listen on + -- port 1234. We call ourselves here, so the branch above is taken. + (_, _, _, sut) <- createProcess $ proc self ["--sut"] + -- 100ms delay to give the SUT time to set up its socket before we try to + -- build connections in the test runner. + threadDelay $ 100 * 1000 + -- ToxTestSuite (the test runner) makes connections to port 1234 to + -- communicate with the SUT. + withArgs (["--print-cpu-time", "--color"] ++ testArgs) ToxTestSuite.main + terminateProcess sut + _ -> + fail "Usage: toxcore-sut [test-args...]" diff --git a/testing/hstox/util.c b/testing/hstox/util.c new file mode 100644 index 00000000..9461596c --- /dev/null +++ b/testing/hstox/util.c @@ -0,0 +1,74 @@ +#include "util.h" + +#include + +char const *type_name(msgpack_object_type type) +{ + switch (type) { + case MSGPACK_OBJECT_NIL: + return "nil"; + + case MSGPACK_OBJECT_BOOLEAN: + return "boolean"; + + case MSGPACK_OBJECT_POSITIVE_INTEGER: + return "positive_integer"; + + case MSGPACK_OBJECT_NEGATIVE_INTEGER: + return "negative_integer"; + + case MSGPACK_OBJECT_FLOAT: + return "float"; + + case MSGPACK_OBJECT_STR: + return "str"; + + case MSGPACK_OBJECT_ARRAY: + return "array"; + + case MSGPACK_OBJECT_MAP: + return "map"; + + case MSGPACK_OBJECT_BIN: + return "bin"; + + case MSGPACK_OBJECT_EXT: + return "ext"; + } + + return ""; +} + +int msgpack_pack_string(msgpack_packer *pk, char const *str) +{ + size_t len = strlen(str); + msgpack_pack_str(pk, len); + return msgpack_pack_str_body(pk, str, len); +} + +int msgpack_pack_vstringf(msgpack_packer *pk, char const *fmt, va_list ap) +{ + char buf[1024]; + vsnprintf(buf, sizeof buf, fmt, ap); + return msgpack_pack_string(pk, buf); +} + +int msgpack_pack_stringf(msgpack_packer *pk, char const *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int res = msgpack_pack_vstringf(pk, fmt, ap); + va_end(ap); + return res; +} + +char const *ssprintf(char const *fmt, ...) +{ + static char buf[1024]; + + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + return buf; +} diff --git a/testing/hstox/util.h b/testing/hstox/util.h new file mode 100644 index 00000000..c00578a2 --- /dev/null +++ b/testing/hstox/util.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#define check_return(err, expr) \ + __extension__({ \ + __typeof__(expr) _r = (expr); \ + if (_r < 0) \ + return err | (__LINE__ << 16); \ + _r; \ + }) + +#define propagate(expr) \ + do { \ + __typeof__(expr) _r = (expr); \ + if (_r != E_OK) \ + return _r; \ + } while (0) + +char const *type_name(msgpack_object_type type); + +// Statically allocated "asprintf". +char const *ssprintf(char const *fmt, ...); + +int msgpack_pack_string(msgpack_packer *pk, char const *str); +int msgpack_pack_stringf(msgpack_packer *pk, char const *fmt, ...); +int msgpack_pack_vstringf(msgpack_packer *pk, char const *fmt, va_list ap); -- cgit v1.2.3