[libc] Add a differential fuzzer for inet_ntop (#207977)

The first byte of the input is used to select the address class and the
size of the output buffer. The rest is used as the input.

We compare the results and also check that our implementation does not
overflow the buffer.

Assisted by Gemini.
This commit is contained in:
Pavel Labath 2026-07-08 13:30:27 +02:00 committed by GitHub
parent 0470117106
commit 2abc19543d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 115 additions and 0 deletions

View file

@ -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
)

View file

@ -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 <arpa/inet.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
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<unsigned long>(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<uint8_t>(impl_dst[i]));
print_details();
__builtin_trap();
}
}
return 0;
}