Skip to content

Commit 3ee3538

Browse files
authored
Merge pull request #17572 from arthurscchan/new-fuzzer-2
OSS-Fuzz: Add new fuzzer for dns packet processing
2 parents ca23d22 + 2609199 commit 3ee3538

3 files changed

Lines changed: 177 additions & 1 deletion

File tree

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,7 @@ if get_option('fuzz-targets')
975975
endif
976976

977977
fuzz_targets = [
978+
'dnspacketroundtrip',
978979
'moadnsparser',
979980
'packetcache',
980981
'proxyprotocol',

pdns/Makefile.am

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1590,7 +1590,8 @@ fuzz_targets_programs = \
15901590
fuzz_target_dnslabeltext_parseRFC1035CharString \
15911591
fuzz_target_yahttp \
15921592
fuzz_target_zoneparsertng \
1593-
fuzz_target_recordcontent
1593+
fuzz_target_recordcontent \
1594+
fuzz_target_dnspacketroundtrip
15941595

15951596
fuzz_targets: $(ARC4RANDOM_LIBS) $(fuzz_targets_programs)
15961597

@@ -1634,6 +1635,32 @@ fuzz_target_moadnsparser_DEPENDENCIES = $(fuzz_targets_deps)
16341635
fuzz_target_moadnsparser_LDFLAGS = $(fuzz_targets_ldflags)
16351636
fuzz_target_moadnsparser_LDADD = $(fuzz_targets_libs)
16361637

1638+
fuzz_target_dnspacketroundtrip_SOURCES = \
1639+
base32.cc base32.hh \
1640+
base64.cc base64.hh \
1641+
dnslabeltext.cc \
1642+
dnsname.cc dnsname.hh \
1643+
dnsparser.cc dnsparser.hh \
1644+
dnsrecords.cc dnsrecords.hh \
1645+
dnswriter.cc dnswriter.hh \
1646+
ednsoptions.cc ednsoptions.hh \
1647+
fuzz_dnspacketroundtrip.cc \
1648+
logger.cc logger.hh \
1649+
logging.cc logging.hh \
1650+
misc.cc misc.hh \
1651+
nsecrecords.cc \
1652+
qtype.cc qtype.hh \
1653+
rcpgenerator.cc rcpgenerator.hh \
1654+
sillyrecords.cc \
1655+
statbag.cc statbag.hh \
1656+
svc-records.cc svc-records.hh \
1657+
unix_utility.cc \
1658+
utility.hh
1659+
1660+
fuzz_target_dnspacketroundtrip_DEPENDENCIES = $(fuzz_targets_deps)
1661+
fuzz_target_dnspacketroundtrip_LDFLAGS = $(fuzz_targets_ldflags)
1662+
fuzz_target_dnspacketroundtrip_LDADD = $(fuzz_targets_libs)
1663+
16371664
fuzz_target_recordcontent_SOURCES = \
16381665
base32.cc base32.hh \
16391666
base64.cc base64.hh \

pdns/fuzz_dnspacketroundtrip.cc

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* This file is part of PowerDNS or dnsdist.
3+
* Copyright -- PowerDNS.COM B.V. and its contributors
4+
*
5+
* This program is free software; you can redistribute it and/or modify
6+
* it under the terms of version 2 of the GNU General Public License as
7+
* published by the Free Software Foundation.
8+
*
9+
* In addition, for the avoidance of any doubt, permission is granted to
10+
* link this program with OpenSSL and to (re)distribute the binaries
11+
* produced as the result of such linking.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*/
22+
23+
#include <array>
24+
25+
#include "dnsparser.hh"
26+
#include "dnsrecords.hh"
27+
#include "dnswriter.hh"
28+
#include "dnsname.hh"
29+
#include "ednsoptions.hh"
30+
#include "qtype.hh"
31+
#include "statbag.hh"
32+
33+
// NOLINTNEXTLINE(readability-identifier-length,bugprone-throwing-static-initialization)
34+
StatBag S;
35+
36+
bool g_slogStructured{false};
37+
38+
static void init()
39+
{
40+
reportAllTypes();
41+
}
42+
43+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
44+
45+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
46+
{
47+
static bool initialized = false;
48+
49+
if (!initialized) {
50+
init();
51+
initialized = true;
52+
}
53+
54+
if (size > std::numeric_limits<uint16_t>::max()) {
55+
return 0;
56+
}
57+
58+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
59+
const char* chars = reinterpret_cast<const char*>(data);
60+
61+
try {
62+
// 1) Feed the raw, untrusted bytes straight to the EDNS OPT option parsers:
63+
// the option TLV decoder in ednsoptions.cc is otherwise unreached.
64+
EDNSOptionViewMap viewOptions;
65+
(void)getEDNSOptions(chars, size, viewOptions);
66+
67+
std::vector<std::pair<uint16_t, std::string>> options;
68+
(void)getEDNSOptionsFromContent(std::string(chars, size), options);
69+
70+
// 2) Drive DNSPacketWriter with attacker-controlled structures (several
71+
// records, attacker rdata, name compression, EDNS OPT writing), the
72+
// full-packet writer paths that the parse/record fuzzers never reach.
73+
size_t pos = 0;
74+
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
75+
const auto getU8 = [&]() -> uint8_t { return pos < size ? data[pos++] : 0; };
76+
const auto getU16 = [&]() -> uint16_t {
77+
const uint16_t highV = getU8();
78+
return static_cast<uint16_t>((highV << 8) | getU8());
79+
};
80+
81+
const uint16_t qtype = getU16();
82+
const DNSName qname(".");
83+
std::vector<uint8_t> packet;
84+
DNSPacketWriter writer(packet, qname, qtype);
85+
86+
static const std::array<const char*, 4> suffixes{".", "example.com.", "a.example.com.", "powerdns.com."};
87+
88+
const unsigned int records = getU8() % 8U;
89+
for (unsigned int i = 0; i < records && pos < size; ++i) {
90+
// A valid name sharing one of a few suffixes (so the name-compression
91+
// paths fire) with an attacker-controlled first label.
92+
const uint8_t labelLen = getU8() % 32U;
93+
std::string label;
94+
for (uint8_t j = 0; j < labelLen && pos < size; ++j) {
95+
label.push_back(static_cast<char>('a' + (getU8() % 26)));
96+
}
97+
const std::string suffix = suffixes.at(getU8() % suffixes.size());
98+
DNSName name{};
99+
try {
100+
if (label.empty()) {
101+
name = DNSName(suffix);
102+
}
103+
else {
104+
label.append(".");
105+
label.append(suffix);
106+
name = DNSName(label);
107+
}
108+
}
109+
catch (...) {
110+
name = qname;
111+
}
112+
113+
const uint16_t rtype = getU16();
114+
const uint16_t rdlen = getU16();
115+
std::string rdata;
116+
for (uint16_t j = 0; j < rdlen && pos < size; ++j) {
117+
rdata.push_back(static_cast<char>(getU8()));
118+
}
119+
120+
writer.startRecord(name, rtype);
121+
writer.xfrBlob(rdata);
122+
}
123+
124+
// EDNS OPT writing fed with the attacker-parsed option vector.
125+
if (!options.empty()) {
126+
DNSPacketWriter::optvect_t optvect;
127+
for (const auto& option : options) {
128+
optvect.emplace_back(option.first, option.second);
129+
}
130+
writer.addOpt(1232, 0, 0, optvect);
131+
}
132+
writer.commit();
133+
134+
// Re-parse the writer's own output.
135+
if (!packet.empty()) {
136+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
137+
MOADNSParser reparse(false, reinterpret_cast<const char*>(packet.data()), packet.size());
138+
}
139+
}
140+
// NOLINTNEXTLINE(bugprone-empty-catch)
141+
catch (const std::exception& e) {
142+
}
143+
// NOLINTNEXTLINE(bugprone-empty-catch)
144+
catch (const PDNSException& e) {
145+
}
146+
147+
return 0;
148+
}

0 commit comments

Comments
 (0)