/src/kea-fuzzer/fuzz_dns.cc
Line | Count | Source |
1 | | // Copyright (C) 2025 Ada Logics Ltd. |
2 | | // |
3 | | // This Source Code Form is subject to the terms of the Mozilla Public |
4 | | // License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
6 | | //////////////////////////////////////////////////////////////////////////////// |
7 | | |
8 | | #include <config.h> |
9 | | #include <fuzzer/FuzzedDataProvider.h> |
10 | | |
11 | | #include <dns/exceptions.h> |
12 | | #include <dns/message.h> |
13 | | #include <dns/messagerenderer.h> |
14 | | #include <dns/name.h> |
15 | | #include <dns/opcode.h> |
16 | | #include <dns/question.h> |
17 | | #include <dns/rcode.h> |
18 | | #include <dns/rdata.h> |
19 | | #include <dns/rdataclass.h> |
20 | | #include <dns/rrclass.h> |
21 | | #include <dns/rrset.h> |
22 | | #include <dns/rrttl.h> |
23 | | #include <dns/rrtype.h> |
24 | | #include <dns/tsig.h> |
25 | | #include <dns/tsigkey.h> |
26 | | #include <dns/tsigrecord.h> |
27 | | #include <dns/master_lexer.h> |
28 | | #include <dns/master_loader.h> |
29 | | #include <util/buffer.h> |
30 | | |
31 | | #include <cstddef> |
32 | | #include <cstdint> |
33 | | #include <memory> |
34 | | #include <sstream> |
35 | | #include <string> |
36 | | #include <vector> |
37 | | |
38 | | using namespace isc::dns; |
39 | | using namespace isc::util; |
40 | | |
41 | 2.94k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
42 | 2.94k | if (size < 2) { |
43 | 1 | return 0; |
44 | 1 | } |
45 | | |
46 | 2.94k | FuzzedDataProvider fdp(data, size); |
47 | | |
48 | | // Get a choice for which fuzzing path to take |
49 | 2.94k | uint8_t choice = fdp.ConsumeIntegral<uint8_t>(); |
50 | | |
51 | | // Reserve some data for different operations |
52 | 2.94k | std::vector<uint8_t> wire_data = fdp.ConsumeBytes<uint8_t>(fdp.remaining_bytes() / 2); |
53 | 2.94k | std::string string_data = fdp.ConsumeRemainingBytesAsString(); |
54 | | |
55 | | // Fuzz DNS Name parsing from string |
56 | 2.94k | if (choice % 8 == 0 && !string_data.empty()) { |
57 | 284 | try { |
58 | 284 | Name name(string_data); |
59 | | // Try various Name operations |
60 | 284 | try { |
61 | 284 | std::string text = name.toText(); |
62 | 284 | OutputBuffer buffer(0); |
63 | 284 | name.toWire(buffer); |
64 | | |
65 | | // Try splitting at different positions |
66 | 284 | if (name.getLabelCount() > 0) { |
67 | 199 | Name stripped = name.split(0); |
68 | 199 | Name reversed = name.reverse(); |
69 | 199 | } |
70 | | |
71 | | // Try comparison operations |
72 | 284 | Name root = Name::ROOT_NAME(); |
73 | 284 | name.compare(root); |
74 | | |
75 | 284 | } catch (const std::exception&) { |
76 | | // Ignore exceptions from operations |
77 | 0 | } |
78 | 284 | } catch (const std::exception&) { |
79 | | // Ignore exceptions from parsing |
80 | 85 | } |
81 | 284 | } |
82 | | |
83 | | // Fuzz DNS Name parsing from wire format |
84 | 2.94k | if (choice % 8 == 1 && !wire_data.empty()) { |
85 | 36 | try { |
86 | 36 | InputBuffer buffer(&wire_data[0], wire_data.size()); |
87 | 36 | Name name(buffer); |
88 | | |
89 | | // Try operations on the parsed name |
90 | 36 | try { |
91 | 36 | name.toText(); |
92 | 36 | name.getLabelCount(); |
93 | 36 | name.getLength(); |
94 | 36 | } catch (const std::exception&) { |
95 | | // Ignore exceptions |
96 | 0 | } |
97 | 36 | } catch (const std::exception&) { |
98 | | // Ignore exceptions from parsing |
99 | 29 | } |
100 | 36 | } |
101 | | |
102 | | // Fuzz DNS Message parsing from wire |
103 | 2.94k | if (choice % 8 == 2 && !wire_data.empty()) { |
104 | 1.09k | try { |
105 | 1.09k | InputBuffer buffer(&wire_data[0], wire_data.size()); |
106 | 1.09k | Message message(Message::PARSE); |
107 | 1.09k | message.fromWire(buffer); |
108 | | |
109 | | // Try various Message operations |
110 | 1.09k | try { |
111 | 1.09k | message.getHeaderFlag(Message::HEADERFLAG_AA); |
112 | 1.09k | message.getRcode(); |
113 | 1.09k | message.getQid(); |
114 | 1.09k | message.getRRCount(Message::SECTION_ANSWER); |
115 | | |
116 | | // Try iterating through sections |
117 | 1.09k | for (int sec = Message::SECTION_QUESTION; |
118 | 1.18k | sec <= Message::SECTION_ADDITIONAL; |
119 | 1.09k | ++sec) { |
120 | 88 | Message::Section section = static_cast<Message::Section>(sec); |
121 | 88 | try { |
122 | 88 | auto it = message.beginSection(section); |
123 | 88 | auto it_end = message.endSection(section); |
124 | 630 | while (it != it_end) { |
125 | 542 | ++it; |
126 | 542 | } |
127 | 88 | } catch (const std::exception&) { |
128 | | // Ignore iteration exceptions |
129 | 22 | } |
130 | 88 | } |
131 | | |
132 | | // Try rendering back to wire |
133 | 1.09k | MessageRenderer renderer; |
134 | 1.09k | try { |
135 | 1.09k | message.toWire(renderer); |
136 | 1.09k | } catch (const std::exception&) { |
137 | | // Ignore rendering exceptions |
138 | 22 | } |
139 | | |
140 | 1.09k | } catch (const std::exception&) { |
141 | | // Ignore operation exceptions |
142 | 0 | } |
143 | 1.09k | } catch (const std::exception&) { |
144 | | // Ignore parsing exceptions |
145 | 1.07k | } |
146 | 1.09k | } |
147 | | |
148 | | // Fuzz Question parsing |
149 | 2.94k | if (choice % 8 == 3 && !wire_data.empty()) { |
150 | 73 | try { |
151 | 73 | InputBuffer buffer(&wire_data[0], wire_data.size()); |
152 | 73 | Question question(buffer); |
153 | | |
154 | 73 | try { |
155 | 73 | question.toText(); |
156 | 73 | question.getName(); |
157 | 73 | question.getType(); |
158 | 73 | question.getClass(); |
159 | | |
160 | 73 | OutputBuffer out_buffer(0); |
161 | 73 | question.toWire(out_buffer); |
162 | 73 | } catch (const std::exception&) { |
163 | | // Ignore operation exceptions |
164 | 0 | } |
165 | 73 | } catch (const std::exception&) { |
166 | | // Ignore parsing exceptions |
167 | 27 | } |
168 | 73 | } |
169 | | |
170 | | // Fuzz RRset operations |
171 | 2.94k | if (choice % 8 == 4 && !string_data.empty() && !wire_data.empty()) { |
172 | 75 | try { |
173 | 75 | Name name(string_data); |
174 | 75 | RRsetPtr rrset = RRsetPtr(new RRset(name, RRClass::IN(), |
175 | 75 | RRType::A(), RRTTL(3600))); |
176 | | |
177 | | // Try parsing RDATA from wire |
178 | 75 | try { |
179 | 75 | InputBuffer buffer(&wire_data[0], wire_data.size()); |
180 | 75 | if (wire_data.size() >= 4) { |
181 | 30 | rdata::ConstRdataPtr rdata = |
182 | 30 | rdata::createRdata(RRType::A(), RRClass::IN(), |
183 | 30 | buffer, wire_data.size()); |
184 | 30 | rrset->addRdata(rdata); |
185 | 30 | } |
186 | 75 | } catch (const std::exception&) { |
187 | | // Ignore RDATA parsing exceptions |
188 | 21 | } |
189 | | |
190 | | // Try RRset operations |
191 | 75 | try { |
192 | 38 | rrset->toText(); |
193 | 38 | rrset->getRdataCount(); |
194 | | |
195 | 38 | OutputBuffer out_buffer(0); |
196 | 38 | rrset->toWire(out_buffer); |
197 | 38 | } catch (const std::exception&) { |
198 | | // Ignore operation exceptions |
199 | 29 | } |
200 | 38 | } catch (const std::exception&) { |
201 | | // Ignore exceptions |
202 | 37 | } |
203 | 75 | } |
204 | | |
205 | | // Fuzz TSIG operations |
206 | 2.94k | if (choice % 8 == 5 && !string_data.empty() && wire_data.size() >= 16) { |
207 | 771 | try { |
208 | | // Try creating a TSIG key |
209 | 771 | TSIGKey key(string_data + ":secret"); |
210 | | |
211 | | // Try creating TSIG RDATA and then a TSIG record |
212 | 771 | try { |
213 | 771 | InputBuffer buffer(&wire_data[0], wire_data.size()); |
214 | | // Try to parse TSIG RDATA |
215 | 771 | rdata::ConstRdataPtr rdata = |
216 | 771 | rdata::createRdata(RRType::TSIG(), RRClass::ANY(), |
217 | 771 | buffer, wire_data.size()); |
218 | 771 | const rdata::any::TSIG& tsig_rdata = |
219 | 771 | dynamic_cast<const rdata::any::TSIG&>(*rdata); |
220 | | |
221 | | // Create a TSIGRecord |
222 | 771 | Name key_name(string_data); |
223 | 771 | TSIGRecord tsig(key_name, tsig_rdata); |
224 | 771 | tsig.toText(); |
225 | | |
226 | 771 | OutputBuffer out_buffer(0); |
227 | 771 | tsig.toWire(out_buffer); |
228 | 771 | } catch (const std::exception&) { |
229 | | // Ignore TSIG parsing exceptions |
230 | 282 | } |
231 | | |
232 | | // Try TSIG context operations (sign operation is public) |
233 | 771 | try { |
234 | 392 | TSIGContext ctx(key); |
235 | | // Try signing some data |
236 | 392 | if (!wire_data.empty()) { |
237 | 392 | ConstTSIGRecordPtr tsig_record = ctx.sign(0, &wire_data[0], wire_data.size()); |
238 | 392 | } |
239 | 392 | } catch (const std::exception&) { |
240 | | // Ignore context exceptions |
241 | 145 | } |
242 | 392 | } catch (const std::exception&) { |
243 | | // Ignore key creation exceptions |
244 | 379 | } |
245 | 771 | } |
246 | | |
247 | | // Fuzz MasterLexer with string input |
248 | 2.94k | if (choice % 8 == 6 && !string_data.empty()) { |
249 | 486 | try { |
250 | 486 | std::istringstream iss(string_data); |
251 | 486 | MasterLexer lexer; |
252 | 486 | lexer.pushSource(iss); |
253 | | |
254 | | // Try tokenizing (loop until we hit EOF token) |
255 | 8.71k | for (int i = 0; i < 100; ++i) { |
256 | 8.65k | try { |
257 | 8.65k | const MasterToken& token = lexer.getNextToken(); |
258 | | |
259 | | // Stop if we hit EOF |
260 | 8.65k | if (token.getType() == MasterToken::END_OF_FILE) { |
261 | 428 | break; |
262 | 428 | } |
263 | | |
264 | | // Access token properties based on type |
265 | 8.22k | if (token.getType() == MasterToken::STRING || |
266 | 5.69k | token.getType() == MasterToken::QSTRING) { |
267 | 5.69k | token.getString(); |
268 | 5.69k | token.getStringRegion(); |
269 | 5.69k | } else if (token.getType() == MasterToken::NUMBER) { |
270 | 0 | token.getNumber(); |
271 | 2.53k | } else if (token.getType() == MasterToken::ERROR) { |
272 | 1.15k | token.getErrorCode(); |
273 | 1.15k | token.getErrorText(); |
274 | 1.15k | } |
275 | 8.22k | } catch (const std::exception&) { |
276 | 0 | break; |
277 | 0 | } |
278 | 8.65k | } |
279 | 486 | } catch (const std::exception&) { |
280 | | // Ignore lexer exceptions |
281 | 0 | } |
282 | 486 | } |
283 | | |
284 | | // Fuzz Message rendering operations |
285 | 2.94k | if (choice % 8 == 7 && !string_data.empty()) { |
286 | 111 | try { |
287 | 111 | Message message(Message::RENDER); |
288 | 111 | message.setQid(fdp.ConsumeIntegral<uint16_t>()); |
289 | 111 | message.setOpcode(Opcode::QUERY()); |
290 | 111 | message.setRcode(Rcode::NOERROR()); |
291 | | |
292 | | // Try setting various flags |
293 | 111 | message.setHeaderFlag(Message::HEADERFLAG_AA, |
294 | 111 | fdp.ConsumeBool()); |
295 | 111 | message.setHeaderFlag(Message::HEADERFLAG_RD, |
296 | 111 | fdp.ConsumeBool()); |
297 | 111 | message.setHeaderFlag(Message::HEADERFLAG_RA, |
298 | 111 | fdp.ConsumeBool()); |
299 | | |
300 | | // Try adding a question |
301 | 111 | try { |
302 | 111 | Name qname(string_data); |
303 | 111 | QuestionPtr question(new Question(qname, RRClass::IN(), |
304 | 111 | RRType::A())); |
305 | 111 | message.addQuestion(question); |
306 | 111 | } catch (const std::exception&) { |
307 | | // Ignore question addition exceptions |
308 | 22 | } |
309 | | |
310 | | // Try rendering |
311 | 111 | try { |
312 | 111 | MessageRenderer renderer; |
313 | 111 | message.toWire(renderer); |
314 | 111 | } catch (const std::exception&) { |
315 | | // Ignore rendering exceptions |
316 | 0 | } |
317 | 111 | } catch (const std::exception&) { |
318 | | // Ignore message creation exceptions |
319 | 0 | } |
320 | 111 | } |
321 | | |
322 | 2.94k | return 0; |
323 | 2.94k | } |