/src/unbound/iterator/iter_scrub.c
Line | Count | Source |
1 | | /* |
2 | | * iterator/iter_scrub.c - scrubbing, normalization, sanitization of DNS msgs. |
3 | | * |
4 | | * Copyright (c) 2007, NLnet Labs. All rights reserved. |
5 | | * |
6 | | * This software is open source. |
7 | | * |
8 | | * Redistribution and use in source and binary forms, with or without |
9 | | * modification, are permitted provided that the following conditions |
10 | | * are met: |
11 | | * |
12 | | * Redistributions of source code must retain the above copyright notice, |
13 | | * this list of conditions and the following disclaimer. |
14 | | * |
15 | | * Redistributions in binary form must reproduce the above copyright notice, |
16 | | * this list of conditions and the following disclaimer in the documentation |
17 | | * and/or other materials provided with the distribution. |
18 | | * |
19 | | * Neither the name of the NLNET LABS nor the names of its contributors may |
20 | | * be used to endorse or promote products derived from this software without |
21 | | * specific prior written permission. |
22 | | * |
23 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
24 | | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
25 | | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
26 | | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
27 | | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
28 | | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
29 | | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
30 | | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
31 | | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
32 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
33 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
34 | | */ |
35 | | |
36 | | /** |
37 | | * \file |
38 | | * |
39 | | * This file has routine(s) for cleaning up incoming DNS messages from |
40 | | * possible useless or malicious junk in it. |
41 | | */ |
42 | | #include "config.h" |
43 | | #include "iterator/iter_scrub.h" |
44 | | #include "iterator/iterator.h" |
45 | | #include "iterator/iter_priv.h" |
46 | | #include "services/cache/rrset.h" |
47 | | #include "util/log.h" |
48 | | #include "util/net_help.h" |
49 | | #include "util/regional.h" |
50 | | #include "util/config_file.h" |
51 | | #include "util/module.h" |
52 | | #include "util/data/msgparse.h" |
53 | | #include "util/data/dname.h" |
54 | | #include "util/data/msgreply.h" |
55 | | #include "util/alloc.h" |
56 | | #include "sldns/sbuffer.h" |
57 | | |
58 | | /** RRset flag used during scrubbing. The RRset is OK. */ |
59 | 2.80k | #define RRSET_SCRUB_OK 0x80 |
60 | | |
61 | | /** remove rrset, update loop variables */ |
62 | | static void |
63 | | remove_rrset(const char* str, sldns_buffer* pkt, struct msg_parse* msg, |
64 | | struct rrset_parse* prev, struct rrset_parse** rrset) |
65 | 82.4k | { |
66 | 82.4k | if(verbosity >= VERB_QUERY && str |
67 | 0 | && (*rrset)->dname_len <= LDNS_MAX_DOMAINLEN) { |
68 | 0 | uint8_t buf[LDNS_MAX_DOMAINLEN+1]; |
69 | 0 | dname_pkt_copy(pkt, buf, (*rrset)->dname); |
70 | 0 | log_nametypeclass(VERB_QUERY, str, buf, |
71 | 0 | (*rrset)->type, ntohs((*rrset)->rrset_class)); |
72 | 0 | } |
73 | 82.4k | if(prev) |
74 | 26.6k | prev->rrset_all_next = (*rrset)->rrset_all_next; |
75 | 55.8k | else msg->rrset_first = (*rrset)->rrset_all_next; |
76 | 82.4k | if(msg->rrset_last == *rrset) |
77 | 2.68k | msg->rrset_last = prev; |
78 | 82.4k | msg->rrset_count --; |
79 | 82.4k | switch((*rrset)->section) { |
80 | 10.6k | case LDNS_SECTION_ANSWER: msg->an_rrsets--; break; |
81 | 41.6k | case LDNS_SECTION_AUTHORITY: msg->ns_rrsets--; break; |
82 | 30.2k | case LDNS_SECTION_ADDITIONAL: msg->ar_rrsets--; break; |
83 | 0 | default: log_assert(0); |
84 | 82.4k | } |
85 | 82.4k | msgparse_bucket_remove(msg, *rrset); |
86 | 82.4k | *rrset = (*rrset)->rrset_all_next; |
87 | 82.4k | } |
88 | | |
89 | | /** return true if rr type has additional names in it */ |
90 | | static int |
91 | | has_additional(uint16_t t) |
92 | 69.8k | { |
93 | 69.8k | switch(t) { |
94 | 2.26k | case LDNS_RR_TYPE_MB: |
95 | 4.73k | case LDNS_RR_TYPE_MD: |
96 | 7.52k | case LDNS_RR_TYPE_MF: |
97 | 10.6k | case LDNS_RR_TYPE_NS: |
98 | 11.4k | case LDNS_RR_TYPE_MX: |
99 | 12.4k | case LDNS_RR_TYPE_KX: |
100 | 13.4k | case LDNS_RR_TYPE_SRV: |
101 | 13.4k | return 1; |
102 | 228 | case LDNS_RR_TYPE_NAPTR: |
103 | | /* TODO: NAPTR not supported, glue stripped off */ |
104 | 228 | return 0; |
105 | 69.8k | } |
106 | 56.0k | return 0; |
107 | 69.8k | } |
108 | | |
109 | | /** get additional name from rrset RR, return false if no name present */ |
110 | | static int |
111 | | get_additional_name(struct rrset_parse* rrset, struct rr_parse* rr, |
112 | | uint8_t** nm, size_t* nmlen, sldns_buffer* pkt) |
113 | 12.5k | { |
114 | 12.5k | size_t offset = 0; |
115 | 12.5k | size_t len, oldpos; |
116 | 12.5k | switch(rrset->type) { |
117 | 3.68k | case LDNS_RR_TYPE_MB: |
118 | 6.08k | case LDNS_RR_TYPE_MD: |
119 | 10.1k | case LDNS_RR_TYPE_MF: |
120 | 10.1k | case LDNS_RR_TYPE_NS: |
121 | 10.1k | offset = 0; |
122 | 10.1k | break; |
123 | 859 | case LDNS_RR_TYPE_MX: |
124 | 1.70k | case LDNS_RR_TYPE_KX: |
125 | 1.70k | offset = 2; |
126 | 1.70k | break; |
127 | 688 | case LDNS_RR_TYPE_SRV: |
128 | 688 | offset = 6; |
129 | 688 | break; |
130 | 0 | case LDNS_RR_TYPE_NAPTR: |
131 | | /* TODO: NAPTR not supported, glue stripped off */ |
132 | 0 | return 0; |
133 | 0 | default: |
134 | 0 | return 0; |
135 | 12.5k | } |
136 | 12.5k | len = sldns_read_uint16(rr->ttl_data+sizeof(uint32_t)); |
137 | 12.5k | if(len < offset+1) |
138 | 7.58k | return 0; /* rdata field too small */ |
139 | 4.94k | *nm = rr->ttl_data+sizeof(uint32_t)+sizeof(uint16_t)+offset; |
140 | 4.94k | oldpos = sldns_buffer_position(pkt); |
141 | 4.94k | sldns_buffer_set_position(pkt, (size_t)(*nm - sldns_buffer_begin(pkt))); |
142 | 4.94k | *nmlen = pkt_dname_len(pkt); |
143 | 4.94k | sldns_buffer_set_position(pkt, oldpos); |
144 | 4.94k | if(*nmlen == 0) |
145 | 0 | return 0; |
146 | 4.94k | return 1; |
147 | 4.94k | } |
148 | | |
149 | | /** Place mark on rrsets in additional section they are OK */ |
150 | | static void |
151 | | mark_additional_rrset(sldns_buffer* pkt, struct msg_parse* msg, |
152 | | struct rrset_parse* rrset) |
153 | 15.4k | { |
154 | | /* Mark A and AAAA for NS as appropriate additional section info. */ |
155 | 15.4k | uint8_t* nm = NULL; |
156 | 15.4k | size_t nmlen = 0; |
157 | 15.4k | struct rr_parse* rr; |
158 | | |
159 | 15.4k | if(!has_additional(rrset->type)) |
160 | 11.5k | return; |
161 | 16.4k | for(rr = rrset->rr_first; rr; rr = rr->next) { |
162 | 12.5k | if(get_additional_name(rrset, rr, &nm, &nmlen, pkt)) { |
163 | | /* mark A */ |
164 | 4.94k | hashvalue_type h = pkt_hash_rrset(pkt, nm, |
165 | 4.94k | LDNS_RR_TYPE_A, rrset->rrset_class, 0); |
166 | 4.94k | struct rrset_parse* r = msgparse_hashtable_lookup( |
167 | 4.94k | msg, pkt, h, 0, nm, nmlen, |
168 | 4.94k | LDNS_RR_TYPE_A, rrset->rrset_class); |
169 | 4.94k | if(r && r->section == LDNS_SECTION_ADDITIONAL) { |
170 | 139 | r->flags |= RRSET_SCRUB_OK; |
171 | 139 | } |
172 | | |
173 | | /* mark AAAA */ |
174 | 4.94k | h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_AAAA, |
175 | 4.94k | rrset->rrset_class, 0); |
176 | 4.94k | r = msgparse_hashtable_lookup(msg, pkt, h, 0, nm, |
177 | 4.94k | nmlen, LDNS_RR_TYPE_AAAA, rrset->rrset_class); |
178 | 4.94k | if(r && r->section == LDNS_SECTION_ADDITIONAL) { |
179 | 85 | r->flags |= RRSET_SCRUB_OK; |
180 | 85 | } |
181 | 4.94k | } |
182 | 12.5k | } |
183 | 3.90k | } |
184 | | |
185 | | /** Get target name of a CNAME */ |
186 | | static int |
187 | | parse_get_cname_target(struct rrset_parse* rrset, uint8_t** sname, |
188 | | size_t* snamelen, sldns_buffer* pkt) |
189 | 130 | { |
190 | 130 | size_t oldpos, dlen; |
191 | 130 | if(rrset->rr_count != 1) { |
192 | 40 | struct rr_parse* sig; |
193 | 40 | verbose(VERB_ALGO, "Found CNAME rrset with " |
194 | 40 | "size > 1: %u", (unsigned)rrset->rr_count); |
195 | | /* use the first CNAME! */ |
196 | 40 | rrset->rr_count = 1; |
197 | 40 | rrset->size = rrset->rr_first->size; |
198 | 584 | for(sig=rrset->rrsig_first; sig; sig=sig->next) |
199 | 544 | rrset->size += sig->size; |
200 | 40 | rrset->rr_last = rrset->rr_first; |
201 | 40 | rrset->rr_first->next = NULL; |
202 | 40 | } |
203 | 130 | if(rrset->rr_first->size < sizeof(uint16_t)+1) |
204 | 60 | return 0; /* CNAME rdata too small */ |
205 | 70 | *sname = rrset->rr_first->ttl_data + sizeof(uint32_t) |
206 | 70 | + sizeof(uint16_t); /* skip ttl, rdatalen */ |
207 | 70 | *snamelen = rrset->rr_first->size - sizeof(uint16_t); |
208 | | |
209 | 70 | if(rrset->rr_first->outside_packet) { |
210 | 0 | if(!dname_valid(*sname, *snamelen)) |
211 | 0 | return 0; |
212 | 0 | return 1; |
213 | 0 | } |
214 | 70 | oldpos = sldns_buffer_position(pkt); |
215 | 70 | sldns_buffer_set_position(pkt, (size_t)(*sname - sldns_buffer_begin(pkt))); |
216 | 70 | dlen = pkt_dname_len(pkt); |
217 | 70 | sldns_buffer_set_position(pkt, oldpos); |
218 | 70 | if(dlen == 0) |
219 | 0 | return 0; /* parse fail on the rdata name */ |
220 | 70 | *snamelen = dlen; |
221 | 70 | return 1; |
222 | 70 | } |
223 | | |
224 | | /** Synthesize CNAME from DNAME, false if too long */ |
225 | | static int |
226 | | synth_cname(uint8_t* qname, size_t qnamelen, struct rrset_parse* dname_rrset, |
227 | | uint8_t* alias, size_t* aliaslen, sldns_buffer* pkt) |
228 | 14 | { |
229 | | /* we already know that sname is a strict subdomain of DNAME owner */ |
230 | 14 | uint8_t* dtarg = NULL; |
231 | 14 | size_t dtarglen; |
232 | 14 | if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen, pkt)) |
233 | 14 | return 0; |
234 | 0 | if(qnamelen <= dname_rrset->dname_len) |
235 | 0 | return 0; |
236 | 0 | if(qnamelen == 0) |
237 | 0 | return 0; |
238 | 0 | log_assert(qnamelen > dname_rrset->dname_len); |
239 | | /* DNAME from com. to net. with qname example.com. -> example.net. */ |
240 | | /* so: \3com\0 to \3net\0 and qname \7example\3com\0 */ |
241 | 0 | *aliaslen = qnamelen + dtarglen - dname_rrset->dname_len; |
242 | 0 | if(*aliaslen > LDNS_MAX_DOMAINLEN) |
243 | 0 | return 0; /* should have been RCODE YXDOMAIN */ |
244 | | /* decompress dnames into buffer, we know it fits */ |
245 | 0 | dname_pkt_copy(pkt, alias, qname); |
246 | 0 | dname_pkt_copy(pkt, alias+(qnamelen-dname_rrset->dname_len), dtarg); |
247 | 0 | return 1; |
248 | 0 | } |
249 | | |
250 | | /** synthesize a CNAME rrset */ |
251 | | static struct rrset_parse* |
252 | | synth_cname_rrset(uint8_t** sname, size_t* snamelen, uint8_t* alias, |
253 | | size_t aliaslen, struct regional* region, struct msg_parse* msg, |
254 | | struct rrset_parse* rrset, struct rrset_parse* prev, |
255 | | struct rrset_parse* nx, sldns_buffer* pkt) |
256 | 0 | { |
257 | 0 | struct rrset_parse* cn = (struct rrset_parse*)regional_alloc(region, |
258 | 0 | sizeof(struct rrset_parse)); |
259 | 0 | if(!cn) |
260 | 0 | return NULL; |
261 | 0 | memset(cn, 0, sizeof(*cn)); |
262 | 0 | cn->rr_first = (struct rr_parse*)regional_alloc(region, |
263 | 0 | sizeof(struct rr_parse)); |
264 | 0 | if(!cn->rr_first) |
265 | 0 | return NULL; |
266 | 0 | cn->rr_last = cn->rr_first; |
267 | | /* CNAME from sname to alias */ |
268 | 0 | cn->dname = (uint8_t*)regional_alloc(region, *snamelen); |
269 | 0 | if(!cn->dname) |
270 | 0 | return NULL; |
271 | 0 | dname_pkt_copy(pkt, cn->dname, *sname); |
272 | 0 | cn->dname_len = *snamelen; |
273 | 0 | cn->type = LDNS_RR_TYPE_CNAME; |
274 | 0 | cn->section = rrset->section; |
275 | 0 | cn->rrset_class = rrset->rrset_class; |
276 | 0 | cn->rr_count = 1; |
277 | 0 | cn->size = sizeof(uint16_t) + aliaslen; |
278 | 0 | cn->hash=pkt_hash_rrset(pkt, cn->dname, cn->type, cn->rrset_class, 0); |
279 | | /* allocate TTL + rdatalen + uncompressed dname */ |
280 | 0 | memset(cn->rr_first, 0, sizeof(struct rr_parse)); |
281 | 0 | cn->rr_first->outside_packet = 1; |
282 | 0 | cn->rr_first->ttl_data = (uint8_t*)regional_alloc(region, |
283 | 0 | sizeof(uint32_t)+sizeof(uint16_t)+aliaslen); |
284 | 0 | if(!cn->rr_first->ttl_data) |
285 | 0 | return NULL; |
286 | 0 | memmove(cn->rr_first->ttl_data, rrset->rr_first->ttl_data, |
287 | 0 | sizeof(uint32_t)); /* RFC6672: synth CNAME TTL == DNAME TTL */ |
288 | | /* Apply cache TTL policy so DNAME and synthesized CNAME stay equal |
289 | | * and respect cache-min-ttl/cache-max-ttl (same as rdata_copy path). */ |
290 | 0 | if(!SERVE_ORIGINAL_TTL) { |
291 | 0 | uint32_t ttl = sldns_read_uint32(cn->rr_first->ttl_data); |
292 | 0 | time_t ttl_t = (time_t)ttl; |
293 | 0 | if(ttl_t < MIN_TTL) ttl_t = MIN_TTL; |
294 | 0 | if(ttl_t > MAX_TTL) ttl_t = MAX_TTL; |
295 | 0 | ttl = (uint32_t)ttl_t; |
296 | 0 | sldns_write_uint32(cn->rr_first->ttl_data, ttl); |
297 | 0 | sldns_write_uint32(rrset->rr_first->ttl_data, ttl); |
298 | 0 | } |
299 | 0 | sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen); |
300 | 0 | memmove(cn->rr_first->ttl_data+6, alias, aliaslen); |
301 | 0 | cn->rr_first->size = sizeof(uint16_t)+aliaslen; |
302 | | |
303 | | /* link it in */ |
304 | 0 | cn->rrset_all_next = nx; |
305 | 0 | if(prev) |
306 | 0 | prev->rrset_all_next = cn; |
307 | 0 | else msg->rrset_first = cn; |
308 | 0 | if(nx == NULL) |
309 | 0 | msg->rrset_last = cn; |
310 | 0 | msg->rrset_count ++; |
311 | 0 | msg->an_rrsets++; |
312 | | /* it is not inserted in the msg hashtable. */ |
313 | |
|
314 | 0 | *sname = cn->rr_first->ttl_data + sizeof(uint32_t)+sizeof(uint16_t); |
315 | 0 | *snamelen = aliaslen; |
316 | 0 | return cn; |
317 | 0 | } |
318 | | |
319 | | /** check if DNAME applies to a name */ |
320 | | static int |
321 | | pkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr) |
322 | 825 | { |
323 | 825 | uint8_t buf1[LDNS_MAX_DOMAINLEN+1]; |
324 | 825 | uint8_t buf2[LDNS_MAX_DOMAINLEN+1]; |
325 | | /* decompress names */ |
326 | 825 | dname_pkt_copy(pkt, buf1, sname); |
327 | 825 | dname_pkt_copy(pkt, buf2, dr); |
328 | 825 | return dname_strict_subdomain_c(buf1, buf2); |
329 | 825 | } |
330 | | |
331 | | /** check subdomain with decompression */ |
332 | | static int |
333 | | pkt_sub(sldns_buffer* pkt, uint8_t* comprname, uint8_t* zone) |
334 | 64.9k | { |
335 | 64.9k | uint8_t buf[LDNS_MAX_DOMAINLEN+1]; |
336 | 64.9k | dname_pkt_copy(pkt, buf, comprname); |
337 | 64.9k | return dname_subdomain_c(buf, zone); |
338 | 64.9k | } |
339 | | |
340 | | /** check subdomain with decompression, compressed is parent */ |
341 | | static int |
342 | | sub_of_pkt(sldns_buffer* pkt, uint8_t* zone, uint8_t* comprname) |
343 | 3.80k | { |
344 | 3.80k | uint8_t buf[LDNS_MAX_DOMAINLEN+1]; |
345 | 3.80k | dname_pkt_copy(pkt, buf, comprname); |
346 | 3.80k | return dname_subdomain_c(zone, buf); |
347 | 3.80k | } |
348 | | |
349 | | /** Check if there are SOA records in the authority section (negative) */ |
350 | | static int |
351 | | soa_in_auth(struct msg_parse* msg) |
352 | 3.92k | { |
353 | 3.92k | struct rrset_parse* rrset; |
354 | 276k | for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next) |
355 | 273k | if(rrset->type == LDNS_RR_TYPE_SOA && |
356 | 2.37k | rrset->section == LDNS_SECTION_AUTHORITY) |
357 | 1.02k | return 1; |
358 | 2.90k | return 0; |
359 | 3.92k | } |
360 | | |
361 | | /** Check if type is allowed in the authority section */ |
362 | | static int |
363 | | type_allowed_in_authority_section(uint16_t tp) |
364 | 0 | { |
365 | 0 | if(tp == LDNS_RR_TYPE_SOA || tp == LDNS_RR_TYPE_NS || |
366 | 0 | tp == LDNS_RR_TYPE_DS || tp == LDNS_RR_TYPE_NSEC || |
367 | 0 | tp == LDNS_RR_TYPE_NSEC3) |
368 | 0 | return 1; |
369 | 0 | return 0; |
370 | 0 | } |
371 | | |
372 | | /** Check if type is allowed in the additional section */ |
373 | | static int |
374 | | type_allowed_in_additional_section(uint16_t tp) |
375 | 0 | { |
376 | 0 | if(tp == LDNS_RR_TYPE_A || tp == LDNS_RR_TYPE_AAAA) |
377 | 0 | return 1; |
378 | 0 | return 0; |
379 | 0 | } |
380 | | |
381 | | /** Shorten RRset */ |
382 | | static void |
383 | | shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count) |
384 | 330 | { |
385 | | /* The too large NS RRset is shortened. This is so that too large |
386 | | * content does not overwhelm the cache. It may make the rrset |
387 | | * bogus if it was signed, and then the domain is not resolved any |
388 | | * more, that is okay, the NS RRset was too large. During a referral |
389 | | * it can be shortened and then the first part of the list could |
390 | | * be used to resolve. The scrub continues to disallow glue for the |
391 | | * removed nameserver RRs and removes that too. Because the glue |
392 | | * is not marked as okay, since the RRs have been removed here. */ |
393 | 330 | int i; |
394 | 330 | struct rr_parse* rr = rrset->rr_first, *prev = NULL; |
395 | 330 | if(!rr) |
396 | 0 | return; |
397 | 330 | for(i=0; i<count; i++) { |
398 | 0 | prev = rr; |
399 | 0 | rr = rr->next; |
400 | 0 | if(!rr) |
401 | 0 | return; /* The RRset is already short. */ |
402 | 0 | } |
403 | 330 | if(verbosity >= VERB_QUERY |
404 | 0 | && rrset->dname_len <= LDNS_MAX_DOMAINLEN) { |
405 | 0 | uint8_t buf[LDNS_MAX_DOMAINLEN+1]; |
406 | 0 | dname_pkt_copy(pkt, buf, rrset->dname); |
407 | 0 | log_nametypeclass(VERB_QUERY, "normalize: shorten RRset:", buf, |
408 | 0 | rrset->type, ntohs(rrset->rrset_class)); |
409 | 0 | } |
410 | | /* remove further rrs */ |
411 | 330 | rrset->rr_last = prev; |
412 | 330 | rrset->rr_count = count; |
413 | 4.76k | while(rr) { |
414 | 4.43k | rrset->size -= rr->size; |
415 | 4.43k | rr = rr->next; |
416 | 4.43k | } |
417 | 330 | if(rrset->rr_last) |
418 | 0 | rrset->rr_last->next = NULL; |
419 | 330 | else rrset->rr_first = NULL; |
420 | 330 | } |
421 | | |
422 | | /** |
423 | | * This routine normalizes a response. This includes removing "irrelevant" |
424 | | * records from the answer and additional sections and (re)synthesizing |
425 | | * CNAMEs from DNAMEs, if present. |
426 | | * |
427 | | * @param pkt: packet. |
428 | | * @param msg: msg to normalize. |
429 | | * @param qinfo: original query. |
430 | | * @param region: where to allocate synthesized CNAMEs. |
431 | | * @param env: module env with config options. |
432 | | * @param zonename: name of server zone. |
433 | | * @return 0 on error. |
434 | | */ |
435 | | static int |
436 | | scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, |
437 | | struct query_info* qinfo, struct regional* region, |
438 | | struct module_env* env, uint8_t* zonename) |
439 | 2.97k | { |
440 | 2.97k | uint8_t* sname = qinfo->qname; |
441 | 2.97k | size_t snamelen = qinfo->qname_len; |
442 | 2.97k | struct rrset_parse* rrset, *prev, *nsset=NULL; |
443 | 2.97k | int cname_length = 0; /* number of CNAMEs, or DNAMEs */ |
444 | | |
445 | 2.97k | if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && |
446 | 2.01k | FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN && |
447 | 1.72k | FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_YXDOMAIN) |
448 | 1.54k | return 1; |
449 | | |
450 | | /* For the ANSWER section, remove all "irrelevant" records and add |
451 | | * synthesized CNAMEs from DNAMEs |
452 | | * This will strip out-of-order CNAMEs as well. */ |
453 | | |
454 | | /* walk through the parse packet rrset list, keep track of previous |
455 | | * for insert and delete ease, and examine every RRset */ |
456 | 1.42k | prev = NULL; |
457 | 1.42k | rrset = msg->rrset_first; |
458 | 5.84k | while(rrset && rrset->section == LDNS_SECTION_ANSWER) { |
459 | 4.46k | if(cname_length > env->cfg->iter_scrub_cname) { |
460 | | /* Too many CNAMEs, or DNAMEs, from the authority |
461 | | * server, scrub down the length to something |
462 | | * shorter. This deletes everything after the limit |
463 | | * is reached. The iterator is going to look up |
464 | | * the content one by one anyway. */ |
465 | 1.18k | remove_rrset("normalize: removing because too many cnames:", |
466 | 1.18k | pkt, msg, prev, &rrset); |
467 | 1.18k | continue; |
468 | 1.18k | } |
469 | 3.28k | if(rrset->type == LDNS_RR_TYPE_DNAME && |
470 | 703 | pkt_strict_sub(pkt, sname, rrset->dname) && |
471 | 351 | pkt_sub(pkt, rrset->dname, zonename)) { |
472 | | /* check if next rrset is correct CNAME. else, |
473 | | * synthesize a CNAME */ |
474 | 14 | struct rrset_parse* nx = rrset->rrset_all_next; |
475 | 14 | uint8_t alias[LDNS_MAX_DOMAINLEN+1]; |
476 | 14 | size_t aliaslen = 0; |
477 | 14 | if(rrset->rr_count != 1) { |
478 | 0 | verbose(VERB_ALGO, "Found DNAME rrset with " |
479 | 0 | "size > 1: %u", |
480 | 0 | (unsigned)rrset->rr_count); |
481 | 0 | return 0; |
482 | 0 | } |
483 | 14 | if(!synth_cname(sname, snamelen, rrset, alias, |
484 | 14 | &aliaslen, pkt)) { |
485 | 14 | verbose(VERB_ALGO, "synthesized CNAME " |
486 | 14 | "too long"); |
487 | 14 | if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) { |
488 | 14 | prev = rrset; |
489 | 14 | rrset = rrset->rrset_all_next; |
490 | 14 | continue; |
491 | 14 | } |
492 | 0 | return 0; |
493 | 14 | } |
494 | 0 | cname_length++; |
495 | 0 | if(nx && nx->type == LDNS_RR_TYPE_CNAME && |
496 | 0 | dname_pkt_compare(pkt, sname, nx->dname) == 0) { |
497 | | /* check next cname */ |
498 | 0 | uint8_t* t = NULL; |
499 | 0 | size_t tlen = 0; |
500 | 0 | if(!parse_get_cname_target(nx, &t, &tlen, pkt)) |
501 | 0 | return 0; |
502 | 0 | if(dname_pkt_compare(pkt, alias, t) == 0) { |
503 | | /* it's OK and better capitalized */ |
504 | 0 | prev = rrset; |
505 | 0 | rrset = nx; |
506 | 0 | continue; |
507 | 0 | } |
508 | | /* synth ourselves */ |
509 | 0 | } |
510 | | /* synth a CNAME rrset */ |
511 | 0 | prev = synth_cname_rrset(&sname, &snamelen, alias, |
512 | 0 | aliaslen, region, msg, rrset, rrset, nx, pkt); |
513 | 0 | if(!prev) { |
514 | 0 | log_err("out of memory synthesizing CNAME"); |
515 | 0 | return 0; |
516 | 0 | } |
517 | 0 | rrset = nx; |
518 | 0 | continue; |
519 | |
|
520 | 0 | } |
521 | | |
522 | | /* The only records in the ANSWER section not allowed to */ |
523 | 3.26k | if(dname_pkt_compare(pkt, sname, rrset->dname) != 0) { |
524 | 2.56k | remove_rrset("normalize: removing irrelevant RRset:", |
525 | 2.56k | pkt, msg, prev, &rrset); |
526 | 2.56k | continue; |
527 | 2.56k | } |
528 | | |
529 | | /* Follow the CNAME chain. */ |
530 | 708 | if(rrset->type == LDNS_RR_TYPE_CNAME) { |
531 | 116 | struct rrset_parse* nx = rrset->rrset_all_next; |
532 | 116 | uint8_t* oldsname = sname; |
533 | 116 | cname_length++; |
534 | | /* see if the next one is a DNAME, if so, swap them */ |
535 | 116 | if(nx && nx->section == LDNS_SECTION_ANSWER && |
536 | 83 | nx->type == LDNS_RR_TYPE_DNAME && |
537 | 24 | nx->rr_count == 1 && |
538 | 11 | pkt_strict_sub(pkt, sname, nx->dname) && |
539 | 9 | pkt_sub(pkt, nx->dname, zonename)) { |
540 | | /* there is a DNAME after this CNAME, it |
541 | | * is in the ANSWER section, and the DNAME |
542 | | * applies to the name we cover */ |
543 | | /* check if the alias of the DNAME equals |
544 | | * this CNAME */ |
545 | 0 | uint8_t alias[LDNS_MAX_DOMAINLEN+1]; |
546 | 0 | size_t aliaslen = 0; |
547 | 0 | uint8_t* t = NULL; |
548 | 0 | size_t tlen = 0; |
549 | 0 | if(synth_cname(sname, snamelen, nx, alias, |
550 | 0 | &aliaslen, pkt) && |
551 | 0 | parse_get_cname_target(rrset, &t, &tlen, pkt) && |
552 | 0 | dname_pkt_compare(pkt, alias, t) == 0) { |
553 | | /* the synthesized CNAME equals the |
554 | | * current CNAME. This CNAME is the |
555 | | * one that the DNAME creates, and this |
556 | | * CNAME is better capitalised */ |
557 | 0 | verbose(VERB_ALGO, "normalize: re-order of DNAME and its CNAME"); |
558 | 0 | if(prev) prev->rrset_all_next = nx; |
559 | 0 | else msg->rrset_first = nx; |
560 | 0 | if(nx->rrset_all_next == NULL) |
561 | 0 | msg->rrset_last = rrset; |
562 | 0 | rrset->rrset_all_next = |
563 | 0 | nx->rrset_all_next; |
564 | 0 | nx->rrset_all_next = rrset; |
565 | | /* prev = nx; unused, enable if there |
566 | | * is other rrset removal code after |
567 | | * this */ |
568 | 0 | } |
569 | 0 | } |
570 | | |
571 | | /* move to next name in CNAME chain */ |
572 | 116 | if(!parse_get_cname_target(rrset, &sname, &snamelen, pkt)) |
573 | 46 | return 0; |
574 | 70 | prev = rrset; |
575 | 70 | rrset = rrset->rrset_all_next; |
576 | | /* in CNAME ANY response, can have data after CNAME */ |
577 | 70 | if(qinfo->qtype == LDNS_RR_TYPE_ANY) { |
578 | 0 | while(rrset && rrset->section == |
579 | 0 | LDNS_SECTION_ANSWER && |
580 | 0 | dname_pkt_compare(pkt, oldsname, |
581 | 0 | rrset->dname) == 0) { |
582 | 0 | if(rrset->type == LDNS_RR_TYPE_NS && |
583 | 0 | rrset->rr_count > env->cfg->iter_scrub_ns) { |
584 | 0 | shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); |
585 | 0 | } |
586 | 0 | prev = rrset; |
587 | 0 | rrset = rrset->rrset_all_next; |
588 | 0 | } |
589 | 0 | } |
590 | 70 | continue; |
591 | 116 | } |
592 | | |
593 | | /* Otherwise, make sure that the RRset matches the qtype. */ |
594 | 592 | if(qinfo->qtype != LDNS_RR_TYPE_ANY && |
595 | 592 | qinfo->qtype != rrset->type) { |
596 | 434 | remove_rrset("normalize: removing irrelevant RRset:", |
597 | 434 | pkt, msg, prev, &rrset); |
598 | 434 | continue; |
599 | 434 | } |
600 | | |
601 | 158 | if(rrset->type == LDNS_RR_TYPE_NS && |
602 | 0 | rrset->rr_count > env->cfg->iter_scrub_ns) { |
603 | 0 | shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); |
604 | 0 | } |
605 | | |
606 | | /* Mark the additional names from relevant rrset as OK. */ |
607 | | /* only for RRsets that match the query name, other ones |
608 | | * will be removed by sanitize, so no additional for them */ |
609 | 158 | if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) |
610 | 158 | mark_additional_rrset(pkt, msg, rrset); |
611 | | |
612 | 158 | prev = rrset; |
613 | 158 | rrset = rrset->rrset_all_next; |
614 | 158 | } |
615 | | |
616 | | /* Mark additional names from AUTHORITY */ |
617 | 22.5k | while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) { |
618 | | /* protect internals of recursor by making sure to del these */ |
619 | 21.1k | if(rrset->type==LDNS_RR_TYPE_DNAME || |
620 | 20.9k | rrset->type==LDNS_RR_TYPE_CNAME || |
621 | 20.7k | rrset->type==LDNS_RR_TYPE_A || |
622 | 19.2k | rrset->type==LDNS_RR_TYPE_AAAA) { |
623 | 2.58k | remove_rrset("normalize: removing irrelevant " |
624 | 2.58k | "RRset:", pkt, msg, prev, &rrset); |
625 | 2.58k | continue; |
626 | 2.58k | } |
627 | | /* Allowed list of types in the authority section */ |
628 | 18.5k | if(env->cfg->harden_unknown_additional && |
629 | 0 | !type_allowed_in_authority_section(rrset->type)) { |
630 | 0 | remove_rrset("normalize: removing irrelevant " |
631 | 0 | "RRset:", pkt, msg, prev, &rrset); |
632 | 0 | continue; |
633 | 0 | } |
634 | | /* only one NS set allowed in authority section */ |
635 | 18.5k | if(rrset->type==LDNS_RR_TYPE_NS) { |
636 | | /* NS set must be pertinent to the query */ |
637 | 3.59k | if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) { |
638 | 231 | remove_rrset("normalize: removing irrelevant " |
639 | 231 | "RRset:", pkt, msg, prev, &rrset); |
640 | 231 | continue; |
641 | 231 | } |
642 | | /* we don't want NS sets for NXDOMAIN answers, |
643 | | * because they could contain poisonous contents, |
644 | | * from. eg. fragmentation attacks, inserted after |
645 | | * long RRSIGs in the packet get to the packet |
646 | | * border and such */ |
647 | | /* also for NODATA answers */ |
648 | 3.36k | if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN || |
649 | 2.90k | (FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR |
650 | 2.36k | && soa_in_auth(msg) && msg->an_rrsets == 0)) { |
651 | 963 | remove_rrset("normalize: removing irrelevant " |
652 | 963 | "RRset:", pkt, msg, prev, &rrset); |
653 | 963 | continue; |
654 | 963 | } |
655 | | /* If the NS set is a promiscuous NS set, scrub that |
656 | | * to remove potential for poisonous contents that |
657 | | * affects other names in the same zone. Remove |
658 | | * promiscuous NS sets in positive answers, that |
659 | | * thus have records in the answer section. Nodata |
660 | | * and nxdomain promiscuous NS sets have been removed |
661 | | * already. Since the NS rrset is scrubbed, its |
662 | | * address records are also not marked to be allowed |
663 | | * and are removed later. */ |
664 | 2.39k | if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR && |
665 | 1.86k | msg->an_rrsets != 0 && |
666 | 717 | env->cfg->iter_scrub_promiscuous) { |
667 | 0 | remove_rrset("normalize: removing promiscuous " |
668 | 0 | "RRset:", pkt, msg, prev, &rrset); |
669 | 0 | continue; |
670 | 0 | } |
671 | | /* Also delete promiscuous NS for other RCODEs */ |
672 | 2.39k | if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR |
673 | 539 | && env->cfg->iter_scrub_promiscuous) { |
674 | 0 | remove_rrset("normalize: removing promiscuous " |
675 | 0 | "RRset:", pkt, msg, prev, &rrset); |
676 | 0 | continue; |
677 | 0 | } |
678 | | /* Also delete promiscuous NS for NOERROR with nodata |
679 | | * for authoritative answers, not for delegations. |
680 | | * NOERROR with an_rrsets!=0 already handled. |
681 | | * Also NOERROR and soa_in_auth already handled. |
682 | | * NOERROR with an_rrsets==0, and not a referral. |
683 | | * referral is (NS not the zonename, noSOA). |
684 | | */ |
685 | 2.39k | if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR |
686 | 1.86k | && msg->an_rrsets == 0 |
687 | 1.14k | && !(dname_pkt_compare(pkt, rrset->dname, |
688 | 1.14k | zonename) != 0 && !soa_in_auth(msg)) |
689 | 41 | && env->cfg->iter_scrub_promiscuous) { |
690 | 0 | remove_rrset("normalize: removing promiscuous " |
691 | 0 | "RRset:", pkt, msg, prev, &rrset); |
692 | 0 | continue; |
693 | 0 | } |
694 | 2.39k | if(nsset == NULL) { |
695 | 336 | nsset = rrset; |
696 | 2.06k | } else { |
697 | 2.06k | remove_rrset("normalize: removing irrelevant " |
698 | 2.06k | "RRset:", pkt, msg, prev, &rrset); |
699 | 2.06k | continue; |
700 | 2.06k | } |
701 | 336 | if(rrset->rr_count > env->cfg->iter_scrub_ns) { |
702 | | /* If this is not a referral, and the NS RRset |
703 | | * is signed, then remove it entirely, so |
704 | | * that when it becomes bogus it does not |
705 | | * make the message that is otherwise fine |
706 | | * into a bogus message. */ |
707 | 336 | if(!(msg->an_rrsets == 0 && |
708 | 287 | FLAGS_GET_RCODE(msg->flags) == |
709 | 287 | LDNS_RCODE_NOERROR && |
710 | 247 | !soa_in_auth(msg) && |
711 | 247 | !(msg->flags & BIT_AA)) && |
712 | 228 | rrset->rrsig_count != 0) { |
713 | 6 | remove_rrset("normalize: removing too large NS " |
714 | 6 | "RRset:", pkt, msg, prev, &rrset); |
715 | 6 | continue; |
716 | 330 | } else { |
717 | 330 | shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); |
718 | 330 | } |
719 | 336 | } |
720 | 336 | } |
721 | | /* if this is type DS and we query for type DS we just got |
722 | | * a referral answer for our type DS query, fix packet */ |
723 | 15.3k | if(rrset->type==LDNS_RR_TYPE_DS && |
724 | 127 | qinfo->qtype == LDNS_RR_TYPE_DS && |
725 | 0 | dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) { |
726 | 0 | rrset->section = LDNS_SECTION_ANSWER; |
727 | 0 | msg->ancount = rrset->rr_count + rrset->rrsig_count; |
728 | 0 | msg->nscount = 0; |
729 | 0 | msg->arcount = 0; |
730 | 0 | msg->an_rrsets = 1; |
731 | 0 | msg->ns_rrsets = 0; |
732 | 0 | msg->ar_rrsets = 0; |
733 | 0 | msg->rrset_count = 1; |
734 | 0 | msg->rrset_first = rrset; |
735 | 0 | msg->rrset_last = rrset; |
736 | 0 | rrset->rrset_all_next = NULL; |
737 | 0 | return 1; |
738 | 0 | } |
739 | 15.3k | mark_additional_rrset(pkt, msg, rrset); |
740 | 15.3k | prev = rrset; |
741 | 15.3k | rrset = rrset->rrset_all_next; |
742 | 15.3k | } |
743 | | |
744 | | /* For each record in the additional section, remove it if it is an |
745 | | * address record and not in the collection of additional names |
746 | | * found in ANSWER and AUTHORITY. */ |
747 | | /* These records have not been marked OK previously */ |
748 | 9.14k | while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) { |
749 | 7.76k | if(rrset->type==LDNS_RR_TYPE_A || |
750 | 6.19k | rrset->type==LDNS_RR_TYPE_AAAA) |
751 | 2.35k | { |
752 | 2.35k | if((rrset->flags & RRSET_SCRUB_OK)) { |
753 | | /* remove flag to clean up flags variable */ |
754 | 224 | rrset->flags &= ~RRSET_SCRUB_OK; |
755 | 2.13k | } else { |
756 | 2.13k | remove_rrset("normalize: removing irrelevant " |
757 | 2.13k | "RRset:", pkt, msg, prev, &rrset); |
758 | 2.13k | continue; |
759 | 2.13k | } |
760 | 2.35k | } |
761 | | /* protect internals of recursor by making sure to del these */ |
762 | 5.63k | if(rrset->type==LDNS_RR_TYPE_DNAME || |
763 | 5.37k | rrset->type==LDNS_RR_TYPE_CNAME || |
764 | 5.22k | rrset->type==LDNS_RR_TYPE_NS) { |
765 | 915 | remove_rrset("normalize: removing irrelevant " |
766 | 915 | "RRset:", pkt, msg, prev, &rrset); |
767 | 915 | continue; |
768 | 915 | } |
769 | | /* Allowed list of types in the additional section */ |
770 | 4.71k | if(env->cfg->harden_unknown_additional && |
771 | 0 | !type_allowed_in_additional_section(rrset->type)) { |
772 | 0 | remove_rrset("normalize: removing irrelevant " |
773 | 0 | "RRset:", pkt, msg, prev, &rrset); |
774 | 0 | continue; |
775 | 0 | } |
776 | 4.71k | prev = rrset; |
777 | 4.71k | rrset = rrset->rrset_all_next; |
778 | 4.71k | } |
779 | | |
780 | 1.37k | return 1; |
781 | 1.37k | } |
782 | | |
783 | | /** |
784 | | * Store potential poison in the cache (only if hardening disabled). |
785 | | * The rrset is stored in the cache but removed from the message. |
786 | | * So that it will be used for infrastructure purposes, but not be |
787 | | * returned to the client. |
788 | | * @param pkt: packet |
789 | | * @param msg: message parsed |
790 | | * @param env: environment with cache |
791 | | * @param rrset: to store. |
792 | | */ |
793 | | static void |
794 | | store_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct module_env* env, |
795 | | struct rrset_parse* rrset) |
796 | 8.40k | { |
797 | 8.40k | struct ub_packed_rrset_key* k; |
798 | 8.40k | struct packed_rrset_data* d; |
799 | 8.40k | struct rrset_ref ref; |
800 | 8.40k | time_t now = *env->now; |
801 | | |
802 | 8.40k | k = alloc_special_obtain(env->alloc); |
803 | 8.40k | if(!k) |
804 | 0 | return; |
805 | 8.40k | k->entry.data = NULL; |
806 | 8.40k | if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) { |
807 | 0 | alloc_special_release(env->alloc, k); |
808 | 0 | return; |
809 | 0 | } |
810 | 8.40k | d = (struct packed_rrset_data*)k->entry.data; |
811 | 8.40k | packed_rrset_ttl_add(d, now); |
812 | 8.40k | ref.key = k; |
813 | 8.40k | ref.id = k->id; |
814 | | /*ignore ret: it was in the cache, ref updated */ |
815 | 8.40k | (void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, now); |
816 | 8.40k | } |
817 | | |
818 | | /** |
819 | | * Check if right hand name in NSEC is within zone |
820 | | * @param pkt: the packet buffer for decompression. |
821 | | * @param rrset: the NSEC rrset |
822 | | * @param zonename: the zone name. |
823 | | * @return true if BAD. |
824 | | */ |
825 | | static int sanitize_nsec_is_overreach(sldns_buffer* pkt, |
826 | | struct rrset_parse* rrset, uint8_t* zonename) |
827 | 1.71k | { |
828 | 1.71k | struct rr_parse* rr; |
829 | 1.71k | uint8_t* rhs; |
830 | 1.71k | size_t len; |
831 | 1.71k | log_assert(rrset->type == LDNS_RR_TYPE_NSEC); |
832 | 4.00k | for(rr = rrset->rr_first; rr; rr = rr->next) { |
833 | 3.04k | size_t pos = sldns_buffer_position(pkt); |
834 | 3.04k | size_t rhspos; |
835 | 3.04k | rhs = rr->ttl_data+4+2; |
836 | 3.04k | len = sldns_read_uint16(rr->ttl_data+4); |
837 | 3.04k | rhspos = rhs-sldns_buffer_begin(pkt); |
838 | 3.04k | sldns_buffer_set_position(pkt, rhspos); |
839 | 3.04k | if(pkt_dname_len(pkt) == 0) { |
840 | | /* malformed */ |
841 | 32 | sldns_buffer_set_position(pkt, pos); |
842 | 32 | return 1; |
843 | 32 | } |
844 | 3.01k | if(sldns_buffer_position(pkt)-rhspos > len) { |
845 | | /* outside of rdata boundaries */ |
846 | 389 | sldns_buffer_set_position(pkt, pos); |
847 | 389 | return 1; |
848 | 389 | } |
849 | 2.62k | sldns_buffer_set_position(pkt, pos); |
850 | 2.62k | if(!pkt_sub(pkt, rhs, zonename)) { |
851 | | /* overreaching */ |
852 | 326 | return 1; |
853 | 326 | } |
854 | 2.62k | } |
855 | | /* all NSEC RRs OK */ |
856 | 964 | return 0; |
857 | 1.71k | } |
858 | | |
859 | | /** Remove individual RRs, if the length is wrong. Returns true if the RRset |
860 | | * has been removed. */ |
861 | | static int |
862 | | scrub_sanitize_rr_length(sldns_buffer* pkt, struct msg_parse* msg, |
863 | | struct rrset_parse* prev, struct rrset_parse** rrset, int* added_ede, |
864 | | struct module_qstate* qstate) |
865 | 14.3k | { |
866 | 14.3k | struct rr_parse* rr, *rr_prev = NULL; |
867 | 259k | for(rr = (*rrset)->rr_first; rr; rr = rr->next) { |
868 | | |
869 | | /* Sanity check for length of records |
870 | | * An A record should be 6 bytes only |
871 | | * (2 bytes for length and 4 for IPv4 addr)*/ |
872 | 250k | if((*rrset)->type == LDNS_RR_TYPE_A && rr->size != 6 ) { |
873 | 101k | if(!*added_ede) { |
874 | 371 | *added_ede = 1; |
875 | 371 | errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.", |
876 | 371 | LDNS_EDE_OTHER); |
877 | 371 | } |
878 | 101k | if(msgparse_rrset_remove_rr("sanitize: removing type A RR of inappropriate length:", |
879 | 101k | pkt, *rrset, rr_prev, rr, NULL, 0)) { |
880 | 3.73k | remove_rrset("sanitize: removing type A RRset of inappropriate length:", |
881 | 3.73k | pkt, msg, prev, rrset); |
882 | 3.73k | return 1; |
883 | 3.73k | } |
884 | 97.5k | continue; |
885 | 101k | } |
886 | | |
887 | | /* Sanity check for length of records |
888 | | * An AAAA record should be 18 bytes only |
889 | | * (2 bytes for length and 16 for IPv6 addr)*/ |
890 | 149k | if((*rrset)->type == LDNS_RR_TYPE_AAAA && rr->size != 18 ) { |
891 | 3.62k | if(!*added_ede) { |
892 | 81 | *added_ede = 1; |
893 | 81 | errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.", |
894 | 81 | LDNS_EDE_OTHER); |
895 | 81 | } |
896 | 3.62k | if(msgparse_rrset_remove_rr("sanitize: removing type AAAA RR of inappropriate length:", |
897 | 3.62k | pkt, *rrset, rr_prev, rr, NULL, 0)) { |
898 | 1.73k | remove_rrset("sanitize: removing type AAAA RRset of inappropriate length:", |
899 | 1.73k | pkt, msg, prev, rrset); |
900 | 1.73k | return 1; |
901 | 1.73k | } |
902 | 1.89k | continue; |
903 | 3.62k | } |
904 | 145k | rr_prev = rr; |
905 | 145k | } |
906 | 8.92k | return 0; |
907 | 14.3k | } |
908 | | |
909 | | /** |
910 | | * Given a response event, remove suspect RRsets from the response. |
911 | | * "Suspect" rrsets are potentially poison. Note that this routine expects |
912 | | * the response to be in a "normalized" state -- that is, all "irrelevant" |
913 | | * RRsets have already been removed, CNAMEs are in order, etc. |
914 | | * |
915 | | * @param pkt: packet. |
916 | | * @param msg: msg to normalize. |
917 | | * @param qinfo: the question originally asked. |
918 | | * @param zonename: name of server zone. |
919 | | * @param env: module environment with config and cache. |
920 | | * @param ie: iterator environment with private address data. |
921 | | * @param qstate: for setting errinf for EDE error messages. |
922 | | * @return 0 on error. |
923 | | */ |
924 | | static int |
925 | | scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg, |
926 | | struct query_info* qinfo, uint8_t* zonename, struct module_env* env, |
927 | | struct iter_env* ie, struct module_qstate* qstate) |
928 | 2.92k | { |
929 | 2.92k | int del_addi = 0; /* if additional-holding rrsets are deleted, we |
930 | | do not trust the normalized additional-A-AAAA any more */ |
931 | 2.92k | uint8_t* ns_rrset_dname = NULL; |
932 | 2.92k | int added_rrlen_ede = 0; |
933 | 2.92k | struct rrset_parse* rrset, *prev; |
934 | 2.92k | prev = NULL; |
935 | 2.92k | rrset = msg->rrset_first; |
936 | | |
937 | | /* the first DNAME is allowed to stay. It needs checking before |
938 | | * it can be used from the cache. After normalization, an initial |
939 | | * DNAME will have a correctly synthesized CNAME after it. */ |
940 | 2.92k | if(rrset && rrset->type == LDNS_RR_TYPE_DNAME && |
941 | 140 | rrset->section == LDNS_SECTION_ANSWER && |
942 | 111 | pkt_strict_sub(pkt, qinfo->qname, rrset->dname) && |
943 | 51 | pkt_sub(pkt, rrset->dname, zonename)) { |
944 | 40 | prev = rrset; /* DNAME allowed to stay in answer section */ |
945 | 40 | rrset = rrset->rrset_all_next; |
946 | 40 | } |
947 | | |
948 | | /* remove all records from the answer section that are |
949 | | * not the same domain name as the query domain name. |
950 | | * The answer section should contain rrsets with the same name |
951 | | * as the question. For DNAMEs a CNAME has been synthesized. |
952 | | * Wildcards have the query name in answer section. |
953 | | * ANY queries get query name in answer section. |
954 | | * Remainders of CNAME chains are cut off and resolved by iterator. */ |
955 | 10.3k | while(rrset && rrset->section == LDNS_SECTION_ANSWER) { |
956 | 7.38k | if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) { |
957 | 6.23k | if(has_additional(rrset->type)) del_addi = 1; |
958 | 6.23k | remove_rrset("sanitize: removing extraneous answer " |
959 | 6.23k | "RRset:", pkt, msg, prev, &rrset); |
960 | 6.23k | continue; |
961 | 6.23k | } |
962 | 1.15k | prev = rrset; |
963 | 1.15k | rrset = rrset->rrset_all_next; |
964 | 1.15k | } |
965 | | |
966 | | /* At this point, we brutally remove ALL rrsets that aren't |
967 | | * children of the originating zone. The idea here is that, |
968 | | * as far as we know, the server that we contacted is ONLY |
969 | | * authoritative for the originating zone. It, of course, MAY |
970 | | * be authoritative for any other zones, and of course, MAY |
971 | | * NOT be authoritative for some subdomains of the originating |
972 | | * zone. */ |
973 | 2.92k | prev = NULL; |
974 | 2.92k | rrset = msg->rrset_first; |
975 | 70.2k | while(rrset) { |
976 | | |
977 | | /* Sanity check for length of records */ |
978 | 67.3k | if(rrset->type == LDNS_RR_TYPE_A || |
979 | 55.7k | rrset->type == LDNS_RR_TYPE_AAAA) { |
980 | 14.3k | if(scrub_sanitize_rr_length(pkt, msg, prev, &rrset, |
981 | 14.3k | &added_rrlen_ede, qstate)) |
982 | 5.46k | continue; |
983 | 14.3k | } |
984 | | |
985 | | /* remove private addresses */ |
986 | 61.8k | if(rrset->type == LDNS_RR_TYPE_A || |
987 | 54.0k | rrset->type == LDNS_RR_TYPE_AAAA || |
988 | 52.9k | rrset->type == LDNS_RR_TYPE_SVCB || |
989 | 52.7k | rrset->type == LDNS_RR_TYPE_HTTPS) { |
990 | | |
991 | | /* do not set servfail since this leads to too |
992 | | * many drops of other people using rfc1918 space */ |
993 | | /* also do not remove entire rrset, unless all records |
994 | | * in it are bad */ |
995 | 9.18k | if(priv_rrset_bad(ie->priv, pkt, rrset)) { |
996 | 0 | remove_rrset(NULL, pkt, msg, prev, &rrset); |
997 | 0 | continue; |
998 | 0 | } |
999 | 9.18k | } |
1000 | | |
1001 | | /* skip DNAME records -- they will always be followed by a |
1002 | | * synthesized CNAME, which will be relevant. |
1003 | | * FIXME: should this do something differently with DNAME |
1004 | | * rrsets NOT in Section.ANSWER? */ |
1005 | | /* But since DNAME records are also subdomains of the zone, |
1006 | | * same check can be used */ |
1007 | | |
1008 | 61.8k | if(!pkt_sub(pkt, rrset->dname, zonename)) { |
1009 | 56.7k | if(msg->an_rrsets == 0 && |
1010 | 51.7k | rrset->type == LDNS_RR_TYPE_NS && |
1011 | 2.24k | rrset->section == LDNS_SECTION_AUTHORITY && |
1012 | 1.15k | FLAGS_GET_RCODE(msg->flags) == |
1013 | 1.15k | LDNS_RCODE_NOERROR && !soa_in_auth(msg) && |
1014 | 211 | sub_of_pkt(pkt, zonename, rrset->dname)) { |
1015 | | /* noerror, nodata and this NS rrset is above |
1016 | | * the zone. This is LAME! |
1017 | | * Leave in the NS for lame classification. */ |
1018 | | /* remove everything from the additional |
1019 | | * (we dont want its glue that was approved |
1020 | | * during the normalize action) */ |
1021 | 211 | del_addi = 1; |
1022 | 56.4k | } else if(!env->cfg->harden_glue && ( |
1023 | 41.7k | rrset->type == LDNS_RR_TYPE_A || |
1024 | 34.1k | rrset->type == LDNS_RR_TYPE_AAAA)) { |
1025 | | /* store in cache! Since it is relevant |
1026 | | * (from normalize) it will be picked up |
1027 | | * from the cache to be used later */ |
1028 | 8.40k | store_rrset(pkt, msg, env, rrset); |
1029 | 8.40k | remove_rrset("sanitize: storing potential " |
1030 | 8.40k | "poison RRset:", pkt, msg, prev, &rrset); |
1031 | 8.40k | continue; |
1032 | 48.0k | } else { |
1033 | 48.0k | if(has_additional(rrset->type)) del_addi = 1; |
1034 | 48.0k | remove_rrset("sanitize: removing potential " |
1035 | 48.0k | "poison RRset:", pkt, msg, prev, &rrset); |
1036 | 48.0k | continue; |
1037 | 48.0k | } |
1038 | 56.7k | } |
1039 | 5.38k | if(rrset->type == LDNS_RR_TYPE_NS && |
1040 | 656 | (rrset->section == LDNS_SECTION_AUTHORITY || |
1041 | 524 | rrset->section == LDNS_SECTION_ANSWER)) { |
1042 | | /* If the type is NS, and we're in the |
1043 | | * answer or authority section, then |
1044 | | * store the dname so we can check |
1045 | | * against the glue records |
1046 | | * further down */ |
1047 | 524 | ns_rrset_dname = rrset->dname; |
1048 | 524 | } |
1049 | 5.38k | if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) { |
1050 | 469 | remove_rrset("sanitize: removing potential " |
1051 | 469 | "poison reference RRset:", pkt, msg, prev, &rrset); |
1052 | 469 | continue; |
1053 | 469 | } |
1054 | | /* check if right hand side of NSEC is within zone */ |
1055 | 4.91k | if(rrset->type == LDNS_RR_TYPE_NSEC && |
1056 | 1.71k | sanitize_nsec_is_overreach(pkt, rrset, zonename)) { |
1057 | 747 | remove_rrset("sanitize: removing overreaching NSEC " |
1058 | 747 | "RRset:", pkt, msg, prev, &rrset); |
1059 | 747 | continue; |
1060 | 747 | } |
1061 | 4.16k | if(env->cfg->harden_unverified_glue && ns_rrset_dname && |
1062 | 0 | rrset->section == LDNS_SECTION_ADDITIONAL && |
1063 | 0 | (rrset->type == LDNS_RR_TYPE_A || rrset->type == LDNS_RR_TYPE_AAAA) && |
1064 | 0 | !pkt_strict_sub(pkt, rrset->dname, ns_rrset_dname)) { |
1065 | | /* We're in the additional section, looking |
1066 | | * at an A/AAAA rrset, have a previous |
1067 | | * delegation point and we notice that |
1068 | | * the glue records are NOT for strict |
1069 | | * subdomains of the delegation. So set a |
1070 | | * flag, recompute the hash for the rrset |
1071 | | * and write the A/AAAA record to cache. |
1072 | | * It'll be retrieved if we can't separately |
1073 | | * resolve the glue */ |
1074 | 0 | rrset->flags = PACKED_RRSET_UNVERIFIED_GLUE; |
1075 | 0 | rrset->hash = pkt_hash_rrset(pkt, rrset->dname, rrset->type, rrset->rrset_class, rrset->flags); |
1076 | 0 | store_rrset(pkt, msg, env, rrset); |
1077 | 0 | remove_rrset("sanitize: storing potential " |
1078 | 0 | "unverified glue reference RRset:", pkt, msg, prev, &rrset); |
1079 | 0 | continue; |
1080 | 0 | } |
1081 | 4.16k | prev = rrset; |
1082 | 4.16k | rrset = rrset->rrset_all_next; |
1083 | 4.16k | } |
1084 | 2.92k | return 1; |
1085 | 2.92k | } |
1086 | | |
1087 | | int |
1088 | | scrub_message(sldns_buffer* pkt, struct msg_parse* msg, |
1089 | | struct query_info* qinfo, uint8_t* zonename, struct regional* region, |
1090 | | struct module_env* env, struct module_qstate* qstate, |
1091 | | struct iter_env* ie) |
1092 | 3.31k | { |
1093 | | /* basic sanity checks */ |
1094 | 3.31k | log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS, |
1095 | 3.31k | qinfo->qclass); |
1096 | 3.31k | if(msg->qdcount > 1) |
1097 | 0 | return 0; |
1098 | 3.31k | if( !(msg->flags&BIT_QR) ) |
1099 | 118 | return 0; |
1100 | 3.19k | msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */ |
1101 | | |
1102 | | /* make sure that a query is echoed back when NOERROR or NXDOMAIN */ |
1103 | | /* this is not required for basic operation but is a forgery |
1104 | | * resistance (security) feature */ |
1105 | 3.19k | if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR || |
1106 | 2.15k | FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN || |
1107 | 1.81k | FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) && |
1108 | 1.58k | msg->qdcount == 0) |
1109 | 43 | return 0; |
1110 | | |
1111 | | /* if a query is echoed back, make sure it is correct. Otherwise, |
1112 | | * this may be not a reply to our query. */ |
1113 | 3.15k | if(msg->qdcount == 1) { |
1114 | 1.72k | if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0) |
1115 | 80 | return 0; |
1116 | 1.64k | if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass) |
1117 | 98 | return 0; |
1118 | 1.64k | } |
1119 | | |
1120 | | /* normalize the response, this cleans up the additional. */ |
1121 | 2.97k | if(!scrub_normalize(pkt, msg, qinfo, region, env, zonename)) |
1122 | 46 | return 0; |
1123 | | /* delete all out-of-zone information */ |
1124 | 2.92k | if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate)) |
1125 | 0 | return 0; |
1126 | 2.92k | return 1; |
1127 | 2.92k | } |