diff --git a/libc/fuzzing/arpa/inet/CMakeLists.txt b/libc/fuzzing/arpa/inet/CMakeLists.txt index 0a940799c448..913290a889c4 100644 --- a/libc/fuzzing/arpa/inet/CMakeLists.txt +++ b/libc/fuzzing/arpa/inet/CMakeLists.txt @@ -7,3 +7,12 @@ add_libc_fuzzer( libc.src.arpa.inet.inet_aton libc.src.string.memcpy ) + +add_libc_fuzzer( + inet_ntop_differential_fuzz + SRCS + inet_ntop_differential_fuzz.cpp + DEPENDS + libc.src.arpa.inet.inet_ntop + libc.src.errno.errno +) diff --git a/libc/fuzzing/arpa/inet/inet_ntop_differential_fuzz.cpp b/libc/fuzzing/arpa/inet/inet_ntop_differential_fuzz.cpp new file mode 100644 index 000000000000..fc286ac62d18 --- /dev/null +++ b/libc/fuzzing/arpa/inet/inet_ntop_differential_fuzz.cpp @@ -0,0 +1,106 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Differential fuzz test for llvm-libc inet_ntop implementation. +/// +//===----------------------------------------------------------------------===// + +#include "src/arpa/inet/inet_ntop.h" +#include +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 1) + return 0; + + uint8_t af_selector = data[0]; + + int af = (af_selector & 0x80) ? AF_INET : AF_INET6; + socklen_t dst_size = data[0] % 64; + + // Extract address bytes. We allocate 16 bytes for IPv6, IPv4 will only use 4. + uint8_t address_bytes[16] = {0}; + size_t payload_size = size - 1; + size_t copy_size = payload_size > 16 ? 16 : payload_size; + memcpy(address_bytes, data + 1, copy_size); + + // Setup buffers for dst + constexpr size_t BUFFER_SIZE = 128; + char ref_dst[BUFFER_SIZE]; + char impl_dst[BUFFER_SIZE]; + + constexpr uint8_t SENTINEL = 0x5a; + memset(ref_dst, SENTINEL, BUFFER_SIZE); + memset(impl_dst, SENTINEL, BUFFER_SIZE); + + // Call reference implementation + const char *ref_res = ::inet_ntop(af, address_bytes, ref_dst, dst_size); + + // Call our implementation + const char *impl_res = + LIBC_NAMESPACE::inet_ntop(af, address_bytes, impl_dst, dst_size); + + auto print_details = [&]() { + fprintf(stderr, + "Details:\n" + " af: %d\n" + " dst_size: %lu\n" + " address_bytes: %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x " + "%02x%02x %02x%02x %02x%02x\n" + " ref_res: %s\n" + " impl_res: %s\n", + af, static_cast(dst_size), address_bytes[0], + address_bytes[1], address_bytes[2], address_bytes[3], + address_bytes[4], address_bytes[5], address_bytes[6], + address_bytes[7], address_bytes[8], address_bytes[9], + address_bytes[10], address_bytes[11], address_bytes[12], + address_bytes[13], address_bytes[14], address_bytes[15], + ref_res ? ref_res : "nullptr", impl_res ? impl_res : "nullptr"); + }; + + // Compare results + if ((ref_res == nullptr) != (impl_res == nullptr)) { + fprintf(stderr, "Success/failure mismatch!\n"); + print_details(); + __builtin_trap(); + } + + if (ref_res != nullptr) { + // Both succeeded. + // Check that returned pointers are correct + if (ref_res != ref_dst || impl_res != impl_dst) { + fprintf(stderr, "Returned pointer does not match destination buffer!\n"); + print_details(); + __builtin_trap(); + } + // Check that strings match + if (strcmp(ref_res, impl_res) != 0) { + fprintf(stderr, "Output string mismatch!\n"); + print_details(); + __builtin_trap(); + } + } + + // Check for out-of-bounds writes + for (size_t i = dst_size; i < BUFFER_SIZE; ++i) { + if (impl_dst[i] != SENTINEL) { + fprintf(stderr, + "Out-of-bounds write detected at index %zu (expected 0x%02x, got " + "0x%02x)!\n", + i, SENTINEL, static_cast(impl_dst[i])); + print_details(); + __builtin_trap(); + } + } + + return 0; +}