|
|
|
/* MIT License
|
|
|
|
*
|
|
|
|
* Copyright (c) The c-ares project and its contributors
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice (including the next
|
|
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
|
|
* Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
* SOFTWARE.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "ares.h"
|
|
|
|
#include "include/ares_buf.h"
|
|
|
|
#include "include/ares_mem.h"
|
|
|
|
|
|
|
|
int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size);
|
|
|
|
|
URI parser/writer for ares_set_servers_csv()/ares_get_servers_csv() (#882)
The DNS server format is insufficient for future configurations, such as
supporting DNS over TLS (DoT) and DNS over HTTPS (DoH), as well as
additional functionality such as domain-specific servers. Already, in
the case where different UDP and TCP ports are used, it is impossible to
represent in the current format.
In order to try to use some standardized format, we are going to define
our own URI schemes that should be parse-able by any URI parser. The new
scheme will only be used when the configuration cannot otherwise be
expressed using the current `ipaddr%iface:port` format, which is the
format used as the nameserver configuration in `/etc/resolv.conf`.
However, the parser `ares_set_servers_csv()` shall accept the new URI
scheme format even when it is not necessary.
This PR implements a URI parser and writer and hooks the basic usage
into `ares_set_servers_csv()` and `ares_get_servers_csv()` as well as
provides updated documentation in the relevant manpages.
We will define these URI schemes:
* `dns://` - Normal DNS server (UDP + TCP). We need to be careful not to
conflict with query params defined in
https://datatracker.ietf.org/doc/html/rfc4501 since we'd technically be
extending this URI scheme. Port defaults to `53`.
* `dns+tls://` - DNS over TLS. Port defaults to `853`.
* `dns+https://` - DNS over HTTPS. Port defaults to `443`.
We initially will define these query parameters (additional arguments
may be required in the future to specify options such as TLS certificate
validation rules):
* `tcpport` - TCP port to use, only for `dns://` scheme. The `port`
specified as part of the `authority` component of the URI will be used
for both UDP and TCP by default, this option will override the TCP port.
* `ipaddr` - Only for `dns+tls://` and `dns+https://`. If the
`authority` component of the URI contains a hostname, this is used to
specify the ip address of the hostname. If not specified, will need to
use a non-secure server to perform a DNS lookup to retrieve this
information. It is always recommended to have both the ip address and
fully qualified domain name specified.
* `hostname` - Only for `dns+tls://` and `dns+https://`. If the
`authority` component of the URI contains an ip address, this is used to
specify the fully qualified domain name of the server. If not specified,
will need to use a non-secure server to perform a DNS reverse lookup to
retrieve this information. It is always recommended to have both the ip
address and fully qualified domain name specified.
* `domain` - If specified, this server is a domain-specific server. Any
queries for this domain will be routed to this server. Multiple servers
may be tagged with the same domain.
Examples:
```
dns://8.8.8.8
dns://[2001:4860:4860::8888]
dns://[fe80::b542:84df:1719:65e3%en0]
dns://192.168.1.1:55
dns://192.168.1.1?tcpport=1153
dns://10.0.1.1?domain=myvpn.com
dns+tls://8.8.8.8?hostname=dns.google
dns+tls://one.one.one.one?ipaddr=1.1.1.1
```
NOTE: While we are defining the scheme for things like domain-specific
servers, DNS over TLS and DNS over HTTPS, the underlying implementations
for those features do not yet exist and therefore will result in errors
if they are attempted to be used.
### Non-compliance in implementation
All these could be easily implemented/fixed if desired, however any such
changes would be of no use to the current c-ares usage of URIs:
* Does not currently support relative references
* Requires use of the authority section, blank is not allowed
* The query string is interpreted to be in
[application/x-www-form-urlencoded](https://en.wikipedia.org/wiki/Application/x-www-form-urlencoded)
format only and will result in parse errors if it is not. This is the
most common format used, however technically not valid to mandate this
format is used. We could add flags in the future to treat the query
string as opaque and leave it to the user to process. Or we could
internally have a list of schemes that use this format.
* [IDNA](https://en.wikipedia.org/wiki/Internationalized_domain_name) is
not supported.
* Does not support hex-encoded IPv4 addresses (this is compliant with RFC3986, but not WHATWG)
Authored-By: Brad House (@bradh352)
2 months ago
|
|
|
#ifdef USE_LEGACY_FUZZERS
|
|
|
|
|
|
|
|
/* This implementation calls the legacy c-ares parsers, which historically
|
|
|
|
* all used different logic and parsing. As of c-ares 1.21.0 these are
|
|
|
|
* simply wrappers around a single parser, and simply convert the parsed
|
|
|
|
* DNS response into the data structures the legacy parsers used which is a
|
|
|
|
* small amount of code and not likely going to vary based on the input data.
|
|
|
|
*
|
|
|
|
* Instead, these days, it makes more sense to test the new parser directly
|
|
|
|
* instead of calling it 10 or 11 times with the same input data to speed up
|
|
|
|
* the number of iterations per second the fuzzer can perform.
|
|
|
|
*
|
|
|
|
* We are keeping this legacy fuzzer test for historic reasons or if someone
|
|
|
|
* finds them of use.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
|
|
|
|
{
|
|
|
|
/* Feed the data into each of the ares_parse_*_reply functions. */
|
|
|
|
struct hostent *host = NULL;
|
|
|
|
struct ares_addrttl info[5];
|
|
|
|
struct ares_addr6ttl info6[5];
|
|
|
|
unsigned char addrv4[4] = { 0x10, 0x20, 0x30, 0x40 };
|
|
|
|
struct ares_srv_reply *srv = NULL;
|
|
|
|
struct ares_mx_reply *mx = NULL;
|
|
|
|
struct ares_txt_reply *txt = NULL;
|
|
|
|
struct ares_soa_reply *soa = NULL;
|
|
|
|
struct ares_naptr_reply *naptr = NULL;
|
|
|
|
struct ares_caa_reply *caa = NULL;
|
|
|
|
struct ares_uri_reply *uri = NULL;
|
|
|
|
int count = 5;
|
|
|
|
ares_parse_a_reply(data, (int)size, &host, info, &count);
|
|
|
|
if (host) {
|
|
|
|
ares_free_hostent(host);
|
|
|
|
}
|
|
|
|
|
|
|
|
host = NULL;
|
|
|
|
count = 5;
|
|
|
|
ares_parse_aaaa_reply(data, (int)size, &host, info6, &count);
|
|
|
|
if (host) {
|
|
|
|
ares_free_hostent(host);
|
|
|
|
}
|
|
|
|
|
|
|
|
host = NULL;
|
|
|
|
ares_parse_ptr_reply(data, (int)size, addrv4, sizeof(addrv4), AF_INET, &host);
|
|
|
|
if (host) {
|
|
|
|
ares_free_hostent(host);
|
|
|
|
}
|
|
|
|
|
|
|
|
host = NULL;
|
|
|
|
ares_parse_ns_reply(data, (int)size, &host);
|
|
|
|
if (host) {
|
|
|
|
ares_free_hostent(host);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_parse_srv_reply(data, (int)size, &srv);
|
|
|
|
if (srv) {
|
|
|
|
ares_free_data(srv);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_parse_mx_reply(data, (int)size, &mx);
|
|
|
|
if (mx) {
|
|
|
|
ares_free_data(mx);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_parse_txt_reply(data, (int)size, &txt);
|
|
|
|
if (txt) {
|
|
|
|
ares_free_data(txt);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_parse_soa_reply(data, (int)size, &soa);
|
|
|
|
if (soa) {
|
|
|
|
ares_free_data(soa);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_parse_naptr_reply(data, (int)size, &naptr);
|
|
|
|
if (naptr) {
|
|
|
|
ares_free_data(naptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_parse_caa_reply(data, (int)size, &caa);
|
|
|
|
if (caa) {
|
|
|
|
ares_free_data(caa);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_parse_uri_reply(data, (int)size, &uri);
|
|
|
|
if (uri) {
|
|
|
|
ares_free_data(uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
|
|
|
|
{
|
|
|
|
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 */
|
|
|
|
if (size > 65535) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
ares_free(datadup);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|