Fuzzing: add fetcher/writers to fuzzing suite

The coverage of the fuzzer is fairly low as it was only testing the
parsing, not trying to read the data from the datastructure or writing
it back out as a DNS message.  Lets extend, and also provide a
FUZZING.md to help people new to fuzzing get up and running.

Authored-By: Brad House (@bradh352)
pull/871/head
Brad House 3 months ago
parent 1b1196809d
commit bb9c616d03
  1. 34
      test/FUZZING.md
  2. 159
      test/ares-test-fuzz.c

34
test/FUZZING.md vendored

@ -0,0 +1,34 @@
# Fuzzing Hints
1. Set compiler that supports fuzzing, this is an example on MacOS using
a homebrew-installed clang/llvm:
```
export CC="/opt/homebrew/Cellar/llvm/18.1.8/bin/clang"
export CXX="/opt/homebrew/Cellar/llvm/18.1.8/bin/clang++"
```
2. Compile c-ares with both ASAN and fuzzing support. We want an optimized
debug build so we will use `RelWithDebInfo`:
```
export CFLAGS="-fsanitize=address,fuzzer-no-link"
export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
export LDFLAGS="-fsanitize=address,fuzzer-no-link"
mkdir buildfuzz
cd buildfuzz
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -G Ninja ..
ninja
```
3. Build the fuzz test itself linked against our fuzzing-enabled build:
```
${CC} -W -Wall -fsanitize=address,fuzzer -I../include -I../src/lib/include -I. -o ares-test-fuzz ../test/ares-test-fuzz.c -L./lib -Wl,-rpath ./lib -lcares
```
4. Run the fuzzer, its better if you can provide seed input but it does pretty
well on its own since it uses coverage data to determine how to proceed.
You can play with other flags etc, like `-jobs=XX` for parallelism. See
https://llvm.org/docs/LibFuzzer.html
```
mkdir corpus
./ares-test-fuzz -max_len=65535 corpus
```

@ -26,6 +26,8 @@
#include <stddef.h>
#include <stdio.h>
#include "ares.h"
#include "ares__buf.h"
#include "ares_mem.h"
int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size);
@ -126,7 +128,12 @@ int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
{
ares_dns_record_t *dnsrec = NULL;
ares_dns_record_t *dnsrec = NULL;
char *printdata = NULL;
ares__buf_t *printmsg = NULL;
size_t i;
unsigned char *datadup = NULL;
size_t datadup_len = 0;
/* There is never a reason to have a size > 65535, it is immediately
* rejected by the parser */
@ -134,11 +141,155 @@ int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
return -1;
}
ares_dns_parse(data, size, 0, &dnsrec);
if (dnsrec) {
ares_dns_record_destroy(dnsrec);
if (ares_dns_parse(data, size, 0, &dnsrec) != ARES_SUCCESS) {
goto done;
}
/* Lets test the message fetchers */
printmsg = ares__buf_create();
if (printmsg == NULL) {
goto done;
}
ares__buf_append_str(printmsg, ";; ->>HEADER<<- opcode: ");
ares__buf_append_str(printmsg, ares_dns_opcode_tostr(ares_dns_record_get_opcode(dnsrec)));
ares__buf_append_str(printmsg, ", status: ");
ares__buf_append_str(printmsg, ares_dns_rcode_tostr(ares_dns_record_get_rcode(dnsrec)));
ares__buf_append_str(printmsg, ", id: ");
ares__buf_append_num_dec(printmsg, (size_t)ares_dns_record_get_id(dnsrec), 0);
ares__buf_append_str(printmsg, "\n;; flags: ");
ares__buf_append_num_hex(printmsg, (size_t)ares_dns_record_get_flags(dnsrec), 0);
ares__buf_append_str(printmsg, "; QUERY: ");
ares__buf_append_num_dec(printmsg, ares_dns_record_query_cnt(dnsrec), 0);
ares__buf_append_str(printmsg, ", ANSWER: ");
ares__buf_append_num_dec(printmsg, ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER), 0);
ares__buf_append_str(printmsg, ", AUTHORITY: ");
ares__buf_append_num_dec(printmsg, ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY), 0);
ares__buf_append_str(printmsg, ", ADDITIONAL: ");
ares__buf_append_num_dec(printmsg, ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ADDITIONAL), 0);
ares__buf_append_str(printmsg, "\n\n");
ares__buf_append_str(printmsg, ";; QUESTION SECTION:\n");
for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) {
const char *name;
ares_dns_rec_type_t qtype;
ares_dns_class_t qclass;
if (ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass) != ARES_SUCCESS) {
goto done;
}
ares__buf_append_str(printmsg, ";");
ares__buf_append_str(printmsg, name);
ares__buf_append_str(printmsg, ".\t\t\t");
ares__buf_append_str(printmsg, ares_dns_class_tostr(qclass));
ares__buf_append_str(printmsg, "\t");
ares__buf_append_str(printmsg, ares_dns_rec_type_tostr(qtype));
ares__buf_append_str(printmsg, "\n");
}
ares__buf_append_str(printmsg, "\n");
for (i = ARES_SECTION_ANSWER; i < ARES_SECTION_ADDITIONAL + 1; i++) {
size_t j;
ares__buf_append_str(printmsg, ";; ");
ares__buf_append_str(printmsg, ares_dns_section_tostr((ares_dns_section_t)i));
ares__buf_append_str(printmsg, " SECTION:\n");
for (j = 0; j < ares_dns_record_rr_cnt(dnsrec, (ares_dns_section_t)i); j++) {
size_t keys_cnt = 0;
const ares_dns_rr_key_t *keys = NULL;
ares_dns_rr_t *rr = NULL;
size_t k;
rr = ares_dns_record_rr_get(dnsrec, (ares_dns_section_t)i, j);
ares__buf_append_str(printmsg, ares_dns_rr_get_name(rr));
ares__buf_append_str(printmsg, ".\t\t\t");
ares__buf_append_str(printmsg, ares_dns_class_tostr(ares_dns_rr_get_class(rr)));
ares__buf_append_str(printmsg, "\t");
ares__buf_append_str(printmsg, ares_dns_rec_type_tostr(ares_dns_rr_get_type(rr)));
ares__buf_append_str(printmsg, "\t");
ares__buf_append_num_dec(printmsg, ares_dns_rr_get_ttl(rr), 0);
ares__buf_append_str(printmsg, "\t");
keys = ares_dns_rr_get_keys(ares_dns_rr_get_type(rr), &keys_cnt);
for (k = 0; k<keys_cnt; k++) {
char buf[256] = "";
ares__buf_append_str(printmsg, ares_dns_rr_key_tostr(keys[k]));
ares__buf_append_str(printmsg, "=");
switch (ares_dns_rr_key_datatype(keys[k])) {
case ARES_DATATYPE_INADDR:
ares_inet_ntop(AF_INET, ares_dns_rr_get_addr(rr, keys[k]), buf, sizeof(buf));
ares__buf_append_str(printmsg, buf);
break;
case ARES_DATATYPE_INADDR6:
ares_inet_ntop(AF_INET6, ares_dns_rr_get_addr6(rr, keys[k]), buf, sizeof(buf));
ares__buf_append_str(printmsg, buf);
break;
case ARES_DATATYPE_U8:
ares__buf_append_num_dec(printmsg, ares_dns_rr_get_u8(rr, keys[k]), 0);
break;
case ARES_DATATYPE_U16:
ares__buf_append_num_dec(printmsg, ares_dns_rr_get_u16(rr, keys[k]), 0);
break;
case ARES_DATATYPE_U32:
ares__buf_append_num_dec(printmsg, ares_dns_rr_get_u32(rr, keys[k]), 0);
break;
case ARES_DATATYPE_NAME:
case ARES_DATATYPE_STR:
ares__buf_append_byte(printmsg, '"');
ares__buf_append_str(printmsg, ares_dns_rr_get_str(rr, keys[k]));
ares__buf_append_byte(printmsg, '"');
break;
case ARES_DATATYPE_BIN:
/* TODO */
break;
case ARES_DATATYPE_BINP:
{
size_t templen;
ares__buf_append_byte(printmsg, '"');
ares__buf_append_str(printmsg, (const char *)ares_dns_rr_get_bin(rr, keys[k], &templen));
ares__buf_append_byte(printmsg, '"');
}
break;
case ARES_DATATYPE_ABINP:
{
size_t a;
for (a=0; a<ares_dns_rr_get_abin_cnt(rr, keys[k]); a++) {
size_t templen;
if (a != 0) {
ares__buf_append_byte(printmsg, ' ');
}
ares__buf_append_byte(printmsg, '"');
ares__buf_append_str(printmsg, (const char *)ares_dns_rr_get_abin(rr, keys[k], a, &templen));
ares__buf_append_byte(printmsg, '"');
}
}
break;
case ARES_DATATYPE_OPT:
/* TODO */
break;
}
ares__buf_append_str(printmsg, " ");
}
ares__buf_append_str(printmsg, "\n");
}
}
ares__buf_append_str(printmsg, ";; SIZE: ");
ares__buf_append_num_dec(printmsg, size, 0);
ares__buf_append_str(printmsg, "\n\n");
printdata = ares__buf_finish_str(printmsg, NULL);
printmsg = NULL;
/* Write it back out as a dns message to test writer */
if (ares_dns_write(dnsrec, &datadup, &datadup_len) != ARES_SUCCESS) {
goto done;
}
done:
ares_dns_record_destroy(dnsrec);
ares__buf_destroy(printmsg);
ares_free(printdata);
return 0;
}

Loading…
Cancel
Save