xref: /openbsd-src/usr.sbin/unbound/iterator/iter_scrub.c (revision 98bc733b08604094f4138174a0ee0bb9faaca4bd)
1933707f3Ssthen /*
2933707f3Ssthen  * iterator/iter_scrub.c - scrubbing, normalization, sanitization of DNS msgs.
3933707f3Ssthen  *
4933707f3Ssthen  * Copyright (c) 2007, NLnet Labs. All rights reserved.
5933707f3Ssthen  *
6933707f3Ssthen  * This software is open source.
7933707f3Ssthen  *
8933707f3Ssthen  * Redistribution and use in source and binary forms, with or without
9933707f3Ssthen  * modification, are permitted provided that the following conditions
10933707f3Ssthen  * are met:
11933707f3Ssthen  *
12933707f3Ssthen  * Redistributions of source code must retain the above copyright notice,
13933707f3Ssthen  * this list of conditions and the following disclaimer.
14933707f3Ssthen  *
15933707f3Ssthen  * Redistributions in binary form must reproduce the above copyright notice,
16933707f3Ssthen  * this list of conditions and the following disclaimer in the documentation
17933707f3Ssthen  * and/or other materials provided with the distribution.
18933707f3Ssthen  *
19933707f3Ssthen  * Neither the name of the NLNET LABS nor the names of its contributors may
20933707f3Ssthen  * be used to endorse or promote products derived from this software without
21933707f3Ssthen  * specific prior written permission.
22933707f3Ssthen  *
23933707f3Ssthen  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
245d76a658Ssthen  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
255d76a658Ssthen  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
265d76a658Ssthen  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
275d76a658Ssthen  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
285d76a658Ssthen  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
295d76a658Ssthen  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
305d76a658Ssthen  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
315d76a658Ssthen  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
325d76a658Ssthen  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
335d76a658Ssthen  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34933707f3Ssthen  */
35933707f3Ssthen 
36933707f3Ssthen /**
37933707f3Ssthen  * \file
38933707f3Ssthen  *
39933707f3Ssthen  * This file has routine(s) for cleaning up incoming DNS messages from
40933707f3Ssthen  * possible useless or malicious junk in it.
41933707f3Ssthen  */
42933707f3Ssthen #include "config.h"
43933707f3Ssthen #include "iterator/iter_scrub.h"
44933707f3Ssthen #include "iterator/iterator.h"
45933707f3Ssthen #include "iterator/iter_priv.h"
46933707f3Ssthen #include "services/cache/rrset.h"
47933707f3Ssthen #include "util/log.h"
48933707f3Ssthen #include "util/net_help.h"
49933707f3Ssthen #include "util/regional.h"
50933707f3Ssthen #include "util/config_file.h"
51933707f3Ssthen #include "util/module.h"
52933707f3Ssthen #include "util/data/msgparse.h"
53933707f3Ssthen #include "util/data/dname.h"
54933707f3Ssthen #include "util/data/msgreply.h"
55933707f3Ssthen #include "util/alloc.h"
56a58bff56Ssthen #include "sldns/sbuffer.h"
57933707f3Ssthen 
58933707f3Ssthen /** RRset flag used during scrubbing. The RRset is OK. */
59933707f3Ssthen #define RRSET_SCRUB_OK	0x80
60933707f3Ssthen 
61933707f3Ssthen /** remove rrset, update loop variables */
62933707f3Ssthen static void
635d76a658Ssthen remove_rrset(const char* str, sldns_buffer* pkt, struct msg_parse* msg,
64933707f3Ssthen 	struct rrset_parse* prev, struct rrset_parse** rrset)
65933707f3Ssthen {
66229e174cSsthen 	if(verbosity >= VERB_QUERY && str
67933707f3Ssthen 		&& (*rrset)->dname_len <= LDNS_MAX_DOMAINLEN) {
68933707f3Ssthen 		uint8_t buf[LDNS_MAX_DOMAINLEN+1];
69933707f3Ssthen 		dname_pkt_copy(pkt, buf, (*rrset)->dname);
70933707f3Ssthen 		log_nametypeclass(VERB_QUERY, str, buf,
71933707f3Ssthen 			(*rrset)->type, ntohs((*rrset)->rrset_class));
72933707f3Ssthen 	}
73933707f3Ssthen 	if(prev)
74933707f3Ssthen 		prev->rrset_all_next = (*rrset)->rrset_all_next;
75933707f3Ssthen 	else	msg->rrset_first = (*rrset)->rrset_all_next;
76933707f3Ssthen 	if(msg->rrset_last == *rrset)
77933707f3Ssthen 		msg->rrset_last = prev;
78933707f3Ssthen 	msg->rrset_count --;
79933707f3Ssthen 	switch((*rrset)->section) {
80933707f3Ssthen 		case LDNS_SECTION_ANSWER: msg->an_rrsets--; break;
81933707f3Ssthen 		case LDNS_SECTION_AUTHORITY: msg->ns_rrsets--; break;
82933707f3Ssthen 		case LDNS_SECTION_ADDITIONAL: msg->ar_rrsets--; break;
83933707f3Ssthen 		default: log_assert(0);
84933707f3Ssthen 	}
85933707f3Ssthen 	msgparse_bucket_remove(msg, *rrset);
86933707f3Ssthen 	*rrset = (*rrset)->rrset_all_next;
87933707f3Ssthen }
88933707f3Ssthen 
89933707f3Ssthen /** return true if rr type has additional names in it */
90933707f3Ssthen static int
91933707f3Ssthen has_additional(uint16_t t)
92933707f3Ssthen {
93933707f3Ssthen 	switch(t) {
94933707f3Ssthen 		case LDNS_RR_TYPE_MB:
95933707f3Ssthen 		case LDNS_RR_TYPE_MD:
96933707f3Ssthen 		case LDNS_RR_TYPE_MF:
97933707f3Ssthen 		case LDNS_RR_TYPE_NS:
98933707f3Ssthen 		case LDNS_RR_TYPE_MX:
99933707f3Ssthen 		case LDNS_RR_TYPE_KX:
100933707f3Ssthen 		case LDNS_RR_TYPE_SRV:
101933707f3Ssthen 			return 1;
102933707f3Ssthen 		case LDNS_RR_TYPE_NAPTR:
103933707f3Ssthen 			/* TODO: NAPTR not supported, glue stripped off */
104933707f3Ssthen 			return 0;
105933707f3Ssthen 	}
106933707f3Ssthen 	return 0;
107933707f3Ssthen }
108933707f3Ssthen 
109933707f3Ssthen /** get additional name from rrset RR, return false if no name present */
110933707f3Ssthen static int
111933707f3Ssthen get_additional_name(struct rrset_parse* rrset, struct rr_parse* rr,
1125d76a658Ssthen 	uint8_t** nm, size_t* nmlen, sldns_buffer* pkt)
113933707f3Ssthen {
114933707f3Ssthen 	size_t offset = 0;
115933707f3Ssthen 	size_t len, oldpos;
116933707f3Ssthen 	switch(rrset->type) {
117933707f3Ssthen 		case LDNS_RR_TYPE_MB:
118933707f3Ssthen 		case LDNS_RR_TYPE_MD:
119933707f3Ssthen 		case LDNS_RR_TYPE_MF:
120933707f3Ssthen 		case LDNS_RR_TYPE_NS:
121933707f3Ssthen 			offset = 0;
122933707f3Ssthen 			break;
123933707f3Ssthen 		case LDNS_RR_TYPE_MX:
124933707f3Ssthen 		case LDNS_RR_TYPE_KX:
125933707f3Ssthen 			offset = 2;
126933707f3Ssthen 			break;
127933707f3Ssthen 		case LDNS_RR_TYPE_SRV:
128933707f3Ssthen 			offset = 6;
129933707f3Ssthen 			break;
130933707f3Ssthen 		case LDNS_RR_TYPE_NAPTR:
131933707f3Ssthen 			/* TODO: NAPTR not supported, glue stripped off */
132933707f3Ssthen 			return 0;
133933707f3Ssthen 		default:
134933707f3Ssthen 			return 0;
135933707f3Ssthen 	}
1365d76a658Ssthen 	len = sldns_read_uint16(rr->ttl_data+sizeof(uint32_t));
137933707f3Ssthen 	if(len < offset+1)
138933707f3Ssthen 		return 0; /* rdata field too small */
139933707f3Ssthen 	*nm = rr->ttl_data+sizeof(uint32_t)+sizeof(uint16_t)+offset;
1405d76a658Ssthen 	oldpos = sldns_buffer_position(pkt);
1415d76a658Ssthen 	sldns_buffer_set_position(pkt, (size_t)(*nm - sldns_buffer_begin(pkt)));
142933707f3Ssthen 	*nmlen = pkt_dname_len(pkt);
1435d76a658Ssthen 	sldns_buffer_set_position(pkt, oldpos);
144933707f3Ssthen 	if(*nmlen == 0)
145933707f3Ssthen 		return 0;
146933707f3Ssthen 	return 1;
147933707f3Ssthen }
148933707f3Ssthen 
149933707f3Ssthen /** Place mark on rrsets in additional section they are OK */
150933707f3Ssthen static void
1515d76a658Ssthen mark_additional_rrset(sldns_buffer* pkt, struct msg_parse* msg,
152933707f3Ssthen 	struct rrset_parse* rrset)
153933707f3Ssthen {
154933707f3Ssthen 	/* Mark A and AAAA for NS as appropriate additional section info. */
155933707f3Ssthen 	uint8_t* nm = NULL;
156933707f3Ssthen 	size_t nmlen = 0;
157933707f3Ssthen 	struct rr_parse* rr;
158933707f3Ssthen 
159933707f3Ssthen 	if(!has_additional(rrset->type))
160933707f3Ssthen 		return;
161933707f3Ssthen 	for(rr = rrset->rr_first; rr; rr = rr->next) {
162933707f3Ssthen 		if(get_additional_name(rrset, rr, &nm, &nmlen, pkt)) {
163933707f3Ssthen 			/* mark A */
16477079be7Ssthen 			hashvalue_type h = pkt_hash_rrset(pkt, nm,
16577079be7Ssthen 				LDNS_RR_TYPE_A, rrset->rrset_class, 0);
166933707f3Ssthen 			struct rrset_parse* r = msgparse_hashtable_lookup(
167933707f3Ssthen 				msg, pkt, h, 0, nm, nmlen,
168933707f3Ssthen 				LDNS_RR_TYPE_A, rrset->rrset_class);
169933707f3Ssthen 			if(r && r->section == LDNS_SECTION_ADDITIONAL) {
170933707f3Ssthen 				r->flags |= RRSET_SCRUB_OK;
171933707f3Ssthen 			}
172933707f3Ssthen 
173933707f3Ssthen 			/* mark AAAA */
174933707f3Ssthen 			h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_AAAA,
175933707f3Ssthen 				rrset->rrset_class, 0);
176933707f3Ssthen 			r = msgparse_hashtable_lookup(msg, pkt, h, 0, nm,
177933707f3Ssthen 				nmlen, LDNS_RR_TYPE_AAAA, rrset->rrset_class);
178933707f3Ssthen 			if(r && r->section == LDNS_SECTION_ADDITIONAL) {
179933707f3Ssthen 				r->flags |= RRSET_SCRUB_OK;
180933707f3Ssthen 			}
181933707f3Ssthen 		}
182933707f3Ssthen 	}
183933707f3Ssthen }
184933707f3Ssthen 
185933707f3Ssthen /** Get target name of a CNAME */
186933707f3Ssthen static int
187933707f3Ssthen parse_get_cname_target(struct rrset_parse* rrset, uint8_t** sname,
18806a13c09Ssthen 	size_t* snamelen, sldns_buffer* pkt)
189933707f3Ssthen {
19006a13c09Ssthen 	size_t oldpos, dlen;
191933707f3Ssthen 	if(rrset->rr_count != 1) {
192933707f3Ssthen 		struct rr_parse* sig;
193933707f3Ssthen 		verbose(VERB_ALGO, "Found CNAME rrset with "
194933707f3Ssthen 			"size > 1: %u", (unsigned)rrset->rr_count);
195933707f3Ssthen 		/* use the first CNAME! */
196933707f3Ssthen 		rrset->rr_count = 1;
197933707f3Ssthen 		rrset->size = rrset->rr_first->size;
198933707f3Ssthen 		for(sig=rrset->rrsig_first; sig; sig=sig->next)
199933707f3Ssthen 			rrset->size += sig->size;
200933707f3Ssthen 		rrset->rr_last = rrset->rr_first;
201933707f3Ssthen 		rrset->rr_first->next = NULL;
202933707f3Ssthen 	}
203933707f3Ssthen 	if(rrset->rr_first->size < sizeof(uint16_t)+1)
204933707f3Ssthen 		return 0; /* CNAME rdata too small */
205933707f3Ssthen 	*sname = rrset->rr_first->ttl_data + sizeof(uint32_t)
206933707f3Ssthen 		+ sizeof(uint16_t); /* skip ttl, rdatalen */
207933707f3Ssthen 	*snamelen = rrset->rr_first->size - sizeof(uint16_t);
20806a13c09Ssthen 
20906a13c09Ssthen 	if(rrset->rr_first->outside_packet) {
21006a13c09Ssthen 		if(!dname_valid(*sname, *snamelen))
21106a13c09Ssthen 			return 0;
21206a13c09Ssthen 		return 1;
21306a13c09Ssthen 	}
21406a13c09Ssthen 	oldpos = sldns_buffer_position(pkt);
21506a13c09Ssthen 	sldns_buffer_set_position(pkt, (size_t)(*sname - sldns_buffer_begin(pkt)));
21606a13c09Ssthen 	dlen = pkt_dname_len(pkt);
21706a13c09Ssthen 	sldns_buffer_set_position(pkt, oldpos);
21806a13c09Ssthen 	if(dlen == 0)
21906a13c09Ssthen 		return 0; /* parse fail on the rdata name */
22006a13c09Ssthen 	*snamelen = dlen;
221933707f3Ssthen 	return 1;
222933707f3Ssthen }
223933707f3Ssthen 
224933707f3Ssthen /** Synthesize CNAME from DNAME, false if too long */
225933707f3Ssthen static int
226933707f3Ssthen synth_cname(uint8_t* qname, size_t qnamelen, struct rrset_parse* dname_rrset,
2275d76a658Ssthen 	uint8_t* alias, size_t* aliaslen, sldns_buffer* pkt)
228933707f3Ssthen {
229933707f3Ssthen 	/* we already know that sname is a strict subdomain of DNAME owner */
230933707f3Ssthen 	uint8_t* dtarg = NULL;
231933707f3Ssthen 	size_t dtarglen;
23206a13c09Ssthen 	if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen, pkt))
233933707f3Ssthen 		return 0;
234ebf5bb73Ssthen 	if(qnamelen <= dname_rrset->dname_len)
235ebf5bb73Ssthen 		return 0;
236ebf5bb73Ssthen 	if(qnamelen == 0)
237ebf5bb73Ssthen 		return 0;
238933707f3Ssthen 	log_assert(qnamelen > dname_rrset->dname_len);
239933707f3Ssthen 	/* DNAME from com. to net. with qname example.com. -> example.net. */
240933707f3Ssthen 	/* so: \3com\0 to \3net\0 and qname \7example\3com\0 */
241933707f3Ssthen 	*aliaslen = qnamelen + dtarglen - dname_rrset->dname_len;
242933707f3Ssthen 	if(*aliaslen > LDNS_MAX_DOMAINLEN)
243933707f3Ssthen 		return 0; /* should have been RCODE YXDOMAIN */
244933707f3Ssthen 	/* decompress dnames into buffer, we know it fits */
245933707f3Ssthen 	dname_pkt_copy(pkt, alias, qname);
246933707f3Ssthen 	dname_pkt_copy(pkt, alias+(qnamelen-dname_rrset->dname_len), dtarg);
247933707f3Ssthen 	return 1;
248933707f3Ssthen }
249933707f3Ssthen 
250933707f3Ssthen /** synthesize a CNAME rrset */
251933707f3Ssthen static struct rrset_parse*
252933707f3Ssthen synth_cname_rrset(uint8_t** sname, size_t* snamelen, uint8_t* alias,
253933707f3Ssthen 	size_t aliaslen, struct regional* region, struct msg_parse* msg,
254933707f3Ssthen 	struct rrset_parse* rrset, struct rrset_parse* prev,
2555d76a658Ssthen 	struct rrset_parse* nx, sldns_buffer* pkt)
256933707f3Ssthen {
257933707f3Ssthen 	struct rrset_parse* cn = (struct rrset_parse*)regional_alloc(region,
258933707f3Ssthen 		sizeof(struct rrset_parse));
259933707f3Ssthen 	if(!cn)
260933707f3Ssthen 		return NULL;
261933707f3Ssthen 	memset(cn, 0, sizeof(*cn));
262933707f3Ssthen 	cn->rr_first = (struct rr_parse*)regional_alloc(region,
263933707f3Ssthen 		sizeof(struct rr_parse));
264933707f3Ssthen 	if(!cn->rr_first)
265933707f3Ssthen 		return NULL;
266933707f3Ssthen 	cn->rr_last = cn->rr_first;
267933707f3Ssthen 	/* CNAME from sname to alias */
268933707f3Ssthen 	cn->dname = (uint8_t*)regional_alloc(region, *snamelen);
269933707f3Ssthen 	if(!cn->dname)
270933707f3Ssthen 		return NULL;
271933707f3Ssthen 	dname_pkt_copy(pkt, cn->dname, *sname);
272933707f3Ssthen 	cn->dname_len = *snamelen;
273933707f3Ssthen 	cn->type = LDNS_RR_TYPE_CNAME;
274933707f3Ssthen 	cn->section = rrset->section;
275933707f3Ssthen 	cn->rrset_class = rrset->rrset_class;
276933707f3Ssthen 	cn->rr_count = 1;
277933707f3Ssthen 	cn->size = sizeof(uint16_t) + aliaslen;
278933707f3Ssthen 	cn->hash=pkt_hash_rrset(pkt, cn->dname, cn->type, cn->rrset_class, 0);
279933707f3Ssthen 	/* allocate TTL + rdatalen + uncompressed dname */
280933707f3Ssthen 	memset(cn->rr_first, 0, sizeof(struct rr_parse));
281933707f3Ssthen 	cn->rr_first->outside_packet = 1;
282933707f3Ssthen 	cn->rr_first->ttl_data = (uint8_t*)regional_alloc(region,
283933707f3Ssthen 		sizeof(uint32_t)+sizeof(uint16_t)+aliaslen);
284933707f3Ssthen 	if(!cn->rr_first->ttl_data)
285933707f3Ssthen 		return NULL;
286f46c52bfSsthen 	memmove(cn->rr_first->ttl_data, rrset->rr_first->ttl_data,
287f46c52bfSsthen 		sizeof(uint32_t)); /* RFC6672: synth CNAME TTL == DNAME TTL */
2885d76a658Ssthen 	sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen);
289933707f3Ssthen 	memmove(cn->rr_first->ttl_data+6, alias, aliaslen);
290933707f3Ssthen 	cn->rr_first->size = sizeof(uint16_t)+aliaslen;
291933707f3Ssthen 
292933707f3Ssthen 	/* link it in */
293933707f3Ssthen 	cn->rrset_all_next = nx;
294933707f3Ssthen 	if(prev)
295933707f3Ssthen 		prev->rrset_all_next = cn;
296933707f3Ssthen 	else	msg->rrset_first = cn;
297933707f3Ssthen 	if(nx == NULL)
298933707f3Ssthen 		msg->rrset_last = cn;
299933707f3Ssthen 	msg->rrset_count ++;
300933707f3Ssthen 	msg->an_rrsets++;
301933707f3Ssthen 	/* it is not inserted in the msg hashtable. */
302933707f3Ssthen 
303933707f3Ssthen 	*sname = cn->rr_first->ttl_data + sizeof(uint32_t)+sizeof(uint16_t);
304933707f3Ssthen 	*snamelen = aliaslen;
305933707f3Ssthen 	return cn;
306933707f3Ssthen }
307933707f3Ssthen 
308933707f3Ssthen /** check if DNAME applies to a name */
309933707f3Ssthen static int
3105d76a658Ssthen pkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr)
311933707f3Ssthen {
312933707f3Ssthen 	uint8_t buf1[LDNS_MAX_DOMAINLEN+1];
313933707f3Ssthen 	uint8_t buf2[LDNS_MAX_DOMAINLEN+1];
314933707f3Ssthen 	/* decompress names */
315933707f3Ssthen 	dname_pkt_copy(pkt, buf1, sname);
316933707f3Ssthen 	dname_pkt_copy(pkt, buf2, dr);
317933707f3Ssthen 	return dname_strict_subdomain_c(buf1, buf2);
318933707f3Ssthen }
319933707f3Ssthen 
320933707f3Ssthen /** check subdomain with decompression */
321933707f3Ssthen static int
3225d76a658Ssthen pkt_sub(sldns_buffer* pkt, uint8_t* comprname, uint8_t* zone)
323933707f3Ssthen {
324933707f3Ssthen 	uint8_t buf[LDNS_MAX_DOMAINLEN+1];
325933707f3Ssthen 	dname_pkt_copy(pkt, buf, comprname);
326933707f3Ssthen 	return dname_subdomain_c(buf, zone);
327933707f3Ssthen }
328933707f3Ssthen 
329933707f3Ssthen /** check subdomain with decompression, compressed is parent */
330933707f3Ssthen static int
3315d76a658Ssthen sub_of_pkt(sldns_buffer* pkt, uint8_t* zone, uint8_t* comprname)
332933707f3Ssthen {
333933707f3Ssthen 	uint8_t buf[LDNS_MAX_DOMAINLEN+1];
334933707f3Ssthen 	dname_pkt_copy(pkt, buf, comprname);
335933707f3Ssthen 	return dname_subdomain_c(zone, buf);
336933707f3Ssthen }
337933707f3Ssthen 
3383150e5f6Ssthen /** Check if there are SOA records in the authority section (negative) */
3393150e5f6Ssthen static int
3403150e5f6Ssthen soa_in_auth(struct msg_parse* msg)
3413150e5f6Ssthen {
3423150e5f6Ssthen 	struct rrset_parse* rrset;
3433150e5f6Ssthen 	for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next)
3443150e5f6Ssthen 		if(rrset->type == LDNS_RR_TYPE_SOA &&
3453150e5f6Ssthen 			rrset->section == LDNS_SECTION_AUTHORITY)
3463150e5f6Ssthen 			return 1;
3473150e5f6Ssthen 	return 0;
3483150e5f6Ssthen }
3493150e5f6Ssthen 
3508b7325afSsthen /** Check if type is allowed in the authority section */
3518b7325afSsthen static int
3528b7325afSsthen type_allowed_in_authority_section(uint16_t tp)
3538b7325afSsthen {
3548b7325afSsthen 	if(tp == LDNS_RR_TYPE_SOA || tp == LDNS_RR_TYPE_NS ||
3558b7325afSsthen 		tp == LDNS_RR_TYPE_DS || tp == LDNS_RR_TYPE_NSEC ||
3568b7325afSsthen 		tp == LDNS_RR_TYPE_NSEC3)
3578b7325afSsthen 		return 1;
3588b7325afSsthen 	return 0;
3598b7325afSsthen }
3608b7325afSsthen 
3618b7325afSsthen /** Check if type is allowed in the additional section */
3628b7325afSsthen static int
3638b7325afSsthen type_allowed_in_additional_section(uint16_t tp)
3648b7325afSsthen {
3658b7325afSsthen 	if(tp == LDNS_RR_TYPE_A || tp == LDNS_RR_TYPE_AAAA)
3668b7325afSsthen 		return 1;
3678b7325afSsthen 	return 0;
3688b7325afSsthen }
3698b7325afSsthen 
370*98bc733bSsthen /** Shorten RRset */
371*98bc733bSsthen static void
372*98bc733bSsthen shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count)
373*98bc733bSsthen {
374*98bc733bSsthen 	/* The too large NS RRset is shortened. This is so that too large
375*98bc733bSsthen 	 * content does not overwhelm the cache. It may make the rrset
376*98bc733bSsthen 	 * bogus if it was signed, and then the domain is not resolved any
377*98bc733bSsthen 	 * more, that is okay, the NS RRset was too large. During a referral
378*98bc733bSsthen 	 * it can be shortened and then the first part of the list could
379*98bc733bSsthen 	 * be used to resolve. The scrub continues to disallow glue for the
380*98bc733bSsthen 	 * removed nameserver RRs and removes that too. Because the glue
381*98bc733bSsthen 	 * is not marked as okay, since the RRs have been removed here. */
382*98bc733bSsthen 	int i;
383*98bc733bSsthen 	struct rr_parse* rr = rrset->rr_first, *prev = NULL;
384*98bc733bSsthen 	if(!rr)
385*98bc733bSsthen 		return;
386*98bc733bSsthen 	for(i=0; i<count; i++) {
387*98bc733bSsthen 		prev = rr;
388*98bc733bSsthen 		rr = rr->next;
389*98bc733bSsthen 		if(!rr)
390*98bc733bSsthen 			return; /* The RRset is already short. */
391*98bc733bSsthen 	}
392*98bc733bSsthen 	if(verbosity >= VERB_QUERY
393*98bc733bSsthen 		&& rrset->dname_len <= LDNS_MAX_DOMAINLEN) {
394*98bc733bSsthen 		uint8_t buf[LDNS_MAX_DOMAINLEN+1];
395*98bc733bSsthen 		dname_pkt_copy(pkt, buf, rrset->dname);
396*98bc733bSsthen 		log_nametypeclass(VERB_QUERY, "normalize: shorten RRset:", buf,
397*98bc733bSsthen 			rrset->type, ntohs(rrset->rrset_class));
398*98bc733bSsthen 	}
399*98bc733bSsthen 	/* remove further rrs */
400*98bc733bSsthen 	rrset->rr_last = prev;
401*98bc733bSsthen 	rrset->rr_count = count;
402*98bc733bSsthen 	while(rr) {
403*98bc733bSsthen 		rrset->size -= rr->size;
404*98bc733bSsthen 		rr = rr->next;
405*98bc733bSsthen 	}
406*98bc733bSsthen 	if(rrset->rr_last)
407*98bc733bSsthen 		rrset->rr_last->next = NULL;
408*98bc733bSsthen 	else	rrset->rr_first = NULL;
409*98bc733bSsthen }
410*98bc733bSsthen 
411933707f3Ssthen /**
412933707f3Ssthen  * This routine normalizes a response. This includes removing "irrelevant"
413933707f3Ssthen  * records from the answer and additional sections and (re)synthesizing
414933707f3Ssthen  * CNAMEs from DNAMEs, if present.
415933707f3Ssthen  *
416933707f3Ssthen  * @param pkt: packet.
417933707f3Ssthen  * @param msg: msg to normalize.
418933707f3Ssthen  * @param qinfo: original query.
419933707f3Ssthen  * @param region: where to allocate synthesized CNAMEs.
4208b7325afSsthen  * @param env: module env with config options.
421933707f3Ssthen  * @return 0 on error.
422933707f3Ssthen  */
423933707f3Ssthen static int
4245d76a658Ssthen scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
4258b7325afSsthen 	struct query_info* qinfo, struct regional* region,
4268b7325afSsthen 	struct module_env* env)
427933707f3Ssthen {
428933707f3Ssthen 	uint8_t* sname = qinfo->qname;
429933707f3Ssthen 	size_t snamelen = qinfo->qname_len;
430933707f3Ssthen 	struct rrset_parse* rrset, *prev, *nsset=NULL;
431*98bc733bSsthen 	int cname_length = 0; /* number of CNAMEs, or DNAMEs */
432933707f3Ssthen 
433933707f3Ssthen 	if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR &&
434933707f3Ssthen 		FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN)
435933707f3Ssthen 		return 1;
436933707f3Ssthen 
437933707f3Ssthen 	/* For the ANSWER section, remove all "irrelevant" records and add
438933707f3Ssthen 	 * synthesized CNAMEs from DNAMEs
439933707f3Ssthen 	 * This will strip out-of-order CNAMEs as well. */
440933707f3Ssthen 
441933707f3Ssthen 	/* walk through the parse packet rrset list, keep track of previous
442933707f3Ssthen 	 * for insert and delete ease, and examine every RRset */
443933707f3Ssthen 	prev = NULL;
444933707f3Ssthen 	rrset = msg->rrset_first;
445933707f3Ssthen 	while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
446*98bc733bSsthen 		if(cname_length > 11 /* env->cfg.iter_scrub_cname */) {
447*98bc733bSsthen 			/* Too many CNAMEs, or DNAMEs, from the authority
448*98bc733bSsthen 			 * server, scrub down the length to something
449*98bc733bSsthen 			 * shorter. This deletes everything after the limit
450*98bc733bSsthen 			 * is reached. The iterator is going to look up
451*98bc733bSsthen 			 * the content one by one anyway. */
452*98bc733bSsthen 			remove_rrset("normalize: removing because too many cnames:",
453*98bc733bSsthen 				pkt, msg, prev, &rrset);
454*98bc733bSsthen 			continue;
455*98bc733bSsthen 		}
456933707f3Ssthen 		if(rrset->type == LDNS_RR_TYPE_DNAME &&
457933707f3Ssthen 			pkt_strict_sub(pkt, sname, rrset->dname)) {
458933707f3Ssthen 			/* check if next rrset is correct CNAME. else,
459933707f3Ssthen 			 * synthesize a CNAME */
460933707f3Ssthen 			struct rrset_parse* nx = rrset->rrset_all_next;
461933707f3Ssthen 			uint8_t alias[LDNS_MAX_DOMAINLEN+1];
462933707f3Ssthen 			size_t aliaslen = 0;
463933707f3Ssthen 			if(rrset->rr_count != 1) {
464933707f3Ssthen 				verbose(VERB_ALGO, "Found DNAME rrset with "
465933707f3Ssthen 					"size > 1: %u",
466933707f3Ssthen 					(unsigned)rrset->rr_count);
467933707f3Ssthen 				return 0;
468933707f3Ssthen 			}
469933707f3Ssthen 			if(!synth_cname(sname, snamelen, rrset, alias,
470933707f3Ssthen 				&aliaslen, pkt)) {
471933707f3Ssthen 				verbose(VERB_ALGO, "synthesized CNAME "
472933707f3Ssthen 					"too long");
473933707f3Ssthen 				return 0;
474933707f3Ssthen 			}
475*98bc733bSsthen 			cname_length++;
476933707f3Ssthen 			if(nx && nx->type == LDNS_RR_TYPE_CNAME &&
477933707f3Ssthen 			   dname_pkt_compare(pkt, sname, nx->dname) == 0) {
478933707f3Ssthen 				/* check next cname */
479933707f3Ssthen 				uint8_t* t = NULL;
480933707f3Ssthen 				size_t tlen = 0;
48106a13c09Ssthen 				if(!parse_get_cname_target(nx, &t, &tlen, pkt))
482933707f3Ssthen 					return 0;
483933707f3Ssthen 				if(dname_pkt_compare(pkt, alias, t) == 0) {
484933707f3Ssthen 					/* it's OK and better capitalized */
485933707f3Ssthen 					prev = rrset;
486933707f3Ssthen 					rrset = nx;
487933707f3Ssthen 					continue;
488933707f3Ssthen 				}
489933707f3Ssthen 				/* synth ourselves */
490933707f3Ssthen 			}
491933707f3Ssthen 			/* synth a CNAME rrset */
492933707f3Ssthen 			prev = synth_cname_rrset(&sname, &snamelen, alias,
493933707f3Ssthen 				aliaslen, region, msg, rrset, rrset, nx, pkt);
494933707f3Ssthen 			if(!prev) {
495933707f3Ssthen 				log_err("out of memory synthesizing CNAME");
496933707f3Ssthen 				return 0;
497933707f3Ssthen 			}
498933707f3Ssthen 			/* FIXME: resolve the conflict between synthesized
499933707f3Ssthen 			 * CNAME ttls and the cache. */
500933707f3Ssthen 			rrset = nx;
501933707f3Ssthen 			continue;
502933707f3Ssthen 
503933707f3Ssthen 		}
504933707f3Ssthen 
505933707f3Ssthen 		/* The only records in the ANSWER section not allowed to */
506933707f3Ssthen 		if(dname_pkt_compare(pkt, sname, rrset->dname) != 0) {
507933707f3Ssthen 			remove_rrset("normalize: removing irrelevant RRset:",
508933707f3Ssthen 				pkt, msg, prev, &rrset);
509933707f3Ssthen 			continue;
510933707f3Ssthen 		}
511933707f3Ssthen 
512933707f3Ssthen 		/* Follow the CNAME chain. */
513933707f3Ssthen 		if(rrset->type == LDNS_RR_TYPE_CNAME) {
51424893edcSsthen 			struct rrset_parse* nx = rrset->rrset_all_next;
515933707f3Ssthen 			uint8_t* oldsname = sname;
516*98bc733bSsthen 			cname_length++;
51724893edcSsthen 			/* see if the next one is a DNAME, if so, swap them */
51824893edcSsthen 			if(nx && nx->section == LDNS_SECTION_ANSWER &&
51924893edcSsthen 				nx->type == LDNS_RR_TYPE_DNAME &&
52024893edcSsthen 				nx->rr_count == 1 &&
52124893edcSsthen 				pkt_strict_sub(pkt, sname, nx->dname)) {
52224893edcSsthen 				/* there is a DNAME after this CNAME, it
52324893edcSsthen 				 * is in the ANSWER section, and the DNAME
52424893edcSsthen 				 * applies to the name we cover */
52524893edcSsthen 				/* check if the alias of the DNAME equals
52624893edcSsthen 				 * this CNAME */
52724893edcSsthen 				uint8_t alias[LDNS_MAX_DOMAINLEN+1];
52824893edcSsthen 				size_t aliaslen = 0;
52924893edcSsthen 				uint8_t* t = NULL;
53024893edcSsthen 				size_t tlen = 0;
53124893edcSsthen 				if(synth_cname(sname, snamelen, nx, alias,
53224893edcSsthen 					&aliaslen, pkt) &&
53306a13c09Ssthen 					parse_get_cname_target(rrset, &t, &tlen, pkt) &&
53424893edcSsthen 			   		dname_pkt_compare(pkt, alias, t) == 0) {
53524893edcSsthen 					/* the synthesized CNAME equals the
53624893edcSsthen 					 * current CNAME.  This CNAME is the
53724893edcSsthen 					 * one that the DNAME creates, and this
53824893edcSsthen 					 * CNAME is better capitalised */
53924893edcSsthen 					verbose(VERB_ALGO, "normalize: re-order of DNAME and its CNAME");
54024893edcSsthen 					if(prev) prev->rrset_all_next = nx;
54124893edcSsthen 					else msg->rrset_first = nx;
54224893edcSsthen 					if(nx->rrset_all_next == NULL)
54324893edcSsthen 						msg->rrset_last = rrset;
54424893edcSsthen 					rrset->rrset_all_next =
54524893edcSsthen 						nx->rrset_all_next;
54624893edcSsthen 					nx->rrset_all_next = rrset;
547452a1548Ssthen 					/* prev = nx; unused, enable if there
548452a1548Ssthen 					 * is other rrset removal code after
549452a1548Ssthen 					 * this */
55024893edcSsthen 				}
55124893edcSsthen 			}
55224893edcSsthen 
55324893edcSsthen 			/* move to next name in CNAME chain */
55406a13c09Ssthen 			if(!parse_get_cname_target(rrset, &sname, &snamelen, pkt))
555933707f3Ssthen 				return 0;
556933707f3Ssthen 			prev = rrset;
557933707f3Ssthen 			rrset = rrset->rrset_all_next;
558933707f3Ssthen 			/* in CNAME ANY response, can have data after CNAME */
559933707f3Ssthen 			if(qinfo->qtype == LDNS_RR_TYPE_ANY) {
560933707f3Ssthen 				while(rrset && rrset->section ==
561933707f3Ssthen 					LDNS_SECTION_ANSWER &&
562933707f3Ssthen 					dname_pkt_compare(pkt, oldsname,
563933707f3Ssthen 					rrset->dname) == 0) {
564*98bc733bSsthen 					if(rrset->type == LDNS_RR_TYPE_NS &&
565*98bc733bSsthen 						rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) {
566*98bc733bSsthen 						shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */);
567*98bc733bSsthen 					}
568933707f3Ssthen 					prev = rrset;
569933707f3Ssthen 					rrset = rrset->rrset_all_next;
570933707f3Ssthen 				}
571933707f3Ssthen 			}
572933707f3Ssthen 			continue;
573933707f3Ssthen 		}
574933707f3Ssthen 
575933707f3Ssthen 		/* Otherwise, make sure that the RRset matches the qtype. */
576933707f3Ssthen 		if(qinfo->qtype != LDNS_RR_TYPE_ANY &&
577933707f3Ssthen 			qinfo->qtype != rrset->type) {
578933707f3Ssthen 			remove_rrset("normalize: removing irrelevant RRset:",
579933707f3Ssthen 				pkt, msg, prev, &rrset);
580933707f3Ssthen 			continue;
581933707f3Ssthen 		}
582933707f3Ssthen 
583*98bc733bSsthen 		if(rrset->type == LDNS_RR_TYPE_NS &&
584*98bc733bSsthen 			rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) {
585*98bc733bSsthen 			shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */);
586*98bc733bSsthen 		}
587*98bc733bSsthen 
588933707f3Ssthen 		/* Mark the additional names from relevant rrset as OK. */
589933707f3Ssthen 		/* only for RRsets that match the query name, other ones
590933707f3Ssthen 		 * will be removed by sanitize, so no additional for them */
591933707f3Ssthen 		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0)
592933707f3Ssthen 			mark_additional_rrset(pkt, msg, rrset);
593933707f3Ssthen 
594933707f3Ssthen 		prev = rrset;
595933707f3Ssthen 		rrset = rrset->rrset_all_next;
596933707f3Ssthen 	}
597933707f3Ssthen 
598933707f3Ssthen 	/* Mark additional names from AUTHORITY */
599933707f3Ssthen 	while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) {
6008b7325afSsthen 		/* protect internals of recursor by making sure to del these */
601933707f3Ssthen 		if(rrset->type==LDNS_RR_TYPE_DNAME ||
602933707f3Ssthen 			rrset->type==LDNS_RR_TYPE_CNAME ||
603933707f3Ssthen 			rrset->type==LDNS_RR_TYPE_A ||
604933707f3Ssthen 			rrset->type==LDNS_RR_TYPE_AAAA) {
605933707f3Ssthen 			remove_rrset("normalize: removing irrelevant "
606933707f3Ssthen 				"RRset:", pkt, msg, prev, &rrset);
607933707f3Ssthen 			continue;
608933707f3Ssthen 		}
6098b7325afSsthen 		/* Allowed list of types in the authority section */
6108b7325afSsthen 		if(env->cfg->harden_unknown_additional &&
6118b7325afSsthen 			!type_allowed_in_authority_section(rrset->type)) {
6128b7325afSsthen 			remove_rrset("normalize: removing irrelevant "
6138b7325afSsthen 				"RRset:", pkt, msg, prev, &rrset);
6148b7325afSsthen 			continue;
6158b7325afSsthen 		}
616933707f3Ssthen 		/* only one NS set allowed in authority section */
617933707f3Ssthen 		if(rrset->type==LDNS_RR_TYPE_NS) {
618933707f3Ssthen 			/* NS set must be pertinent to the query */
619933707f3Ssthen 			if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) {
620933707f3Ssthen 				remove_rrset("normalize: removing irrelevant "
621933707f3Ssthen 					"RRset:", pkt, msg, prev, &rrset);
622933707f3Ssthen 				continue;
623933707f3Ssthen 			}
6243150e5f6Ssthen 			/* we don't want NS sets for NXDOMAIN answers,
6253150e5f6Ssthen 			 * because they could contain poisonous contents,
6263150e5f6Ssthen 			 * from. eg. fragmentation attacks, inserted after
6273150e5f6Ssthen 			 * long RRSIGs in the packet get to the packet
6283150e5f6Ssthen 			 * border and such */
6293150e5f6Ssthen 			/* also for NODATA answers */
6303150e5f6Ssthen 			if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN ||
6313150e5f6Ssthen 			   (FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR
6323150e5f6Ssthen 			    && soa_in_auth(msg) && msg->an_rrsets == 0)) {
6333150e5f6Ssthen 				remove_rrset("normalize: removing irrelevant "
6343150e5f6Ssthen 					"RRset:", pkt, msg, prev, &rrset);
6353150e5f6Ssthen 				continue;
6363150e5f6Ssthen 			}
637933707f3Ssthen 			if(nsset == NULL) {
638933707f3Ssthen 				nsset = rrset;
639933707f3Ssthen 			} else {
640933707f3Ssthen 				remove_rrset("normalize: removing irrelevant "
641933707f3Ssthen 					"RRset:", pkt, msg, prev, &rrset);
642933707f3Ssthen 				continue;
643933707f3Ssthen 			}
644*98bc733bSsthen 			if(rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) {
645*98bc733bSsthen 				/* If this is not a referral, and the NS RRset
646*98bc733bSsthen 				 * is signed, then remove it entirely, so
647*98bc733bSsthen 				 * that when it becomes bogus it does not
648*98bc733bSsthen 				 * make the message that is otherwise fine
649*98bc733bSsthen 				 * into a bogus message. */
650*98bc733bSsthen 				if(!(msg->an_rrsets == 0 &&
651*98bc733bSsthen 					FLAGS_GET_RCODE(msg->flags) ==
652*98bc733bSsthen 					LDNS_RCODE_NOERROR &&
653*98bc733bSsthen 					!soa_in_auth(msg) &&
654*98bc733bSsthen 					!(msg->flags & BIT_AA)) &&
655*98bc733bSsthen 					rrset->rrsig_count != 0) {
656*98bc733bSsthen 					remove_rrset("normalize: removing too large NS "
657*98bc733bSsthen 						"RRset:", pkt, msg, prev, &rrset);
658*98bc733bSsthen 					continue;
659*98bc733bSsthen 				} else {
660*98bc733bSsthen 					shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */);
661*98bc733bSsthen 				}
662*98bc733bSsthen 			}
663933707f3Ssthen 		}
664bdfc4d55Sflorian 		/* if this is type DS and we query for type DS we just got
665bdfc4d55Sflorian 		 * a referral answer for our type DS query, fix packet */
666bdfc4d55Sflorian 		if(rrset->type==LDNS_RR_TYPE_DS &&
667bdfc4d55Sflorian 			qinfo->qtype == LDNS_RR_TYPE_DS &&
668bdfc4d55Sflorian 			dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) {
669bdfc4d55Sflorian 			rrset->section = LDNS_SECTION_ANSWER;
670bdfc4d55Sflorian 			msg->ancount = rrset->rr_count + rrset->rrsig_count;
671bdfc4d55Sflorian 			msg->nscount = 0;
672bdfc4d55Sflorian 			msg->arcount = 0;
673bdfc4d55Sflorian 			msg->an_rrsets = 1;
674bdfc4d55Sflorian 			msg->ns_rrsets = 0;
675bdfc4d55Sflorian 			msg->ar_rrsets = 0;
676bdfc4d55Sflorian 			msg->rrset_count = 1;
677bdfc4d55Sflorian 			msg->rrset_first = rrset;
678bdfc4d55Sflorian 			msg->rrset_last = rrset;
679bdfc4d55Sflorian 			rrset->rrset_all_next = NULL;
680bdfc4d55Sflorian 			return 1;
681bdfc4d55Sflorian 		}
682933707f3Ssthen 		mark_additional_rrset(pkt, msg, rrset);
683933707f3Ssthen 		prev = rrset;
684933707f3Ssthen 		rrset = rrset->rrset_all_next;
685933707f3Ssthen 	}
686933707f3Ssthen 
687933707f3Ssthen 	/* For each record in the additional section, remove it if it is an
688933707f3Ssthen 	 * address record and not in the collection of additional names
689933707f3Ssthen 	 * found in ANSWER and AUTHORITY. */
690933707f3Ssthen 	/* These records have not been marked OK previously */
691933707f3Ssthen 	while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) {
692933707f3Ssthen 		if(rrset->type==LDNS_RR_TYPE_A ||
693933707f3Ssthen 			rrset->type==LDNS_RR_TYPE_AAAA)
694933707f3Ssthen 		{
695933707f3Ssthen 			if((rrset->flags & RRSET_SCRUB_OK)) {
696933707f3Ssthen 				/* remove flag to clean up flags variable */
697933707f3Ssthen 				rrset->flags &= ~RRSET_SCRUB_OK;
698933707f3Ssthen 			} else {
699933707f3Ssthen 				remove_rrset("normalize: removing irrelevant "
700933707f3Ssthen 					"RRset:", pkt, msg, prev, &rrset);
701933707f3Ssthen 				continue;
702933707f3Ssthen 			}
703933707f3Ssthen 		}
7048b7325afSsthen 		/* protect internals of recursor by making sure to del these */
705933707f3Ssthen 		if(rrset->type==LDNS_RR_TYPE_DNAME ||
706933707f3Ssthen 			rrset->type==LDNS_RR_TYPE_CNAME ||
707933707f3Ssthen 			rrset->type==LDNS_RR_TYPE_NS) {
708933707f3Ssthen 			remove_rrset("normalize: removing irrelevant "
709933707f3Ssthen 				"RRset:", pkt, msg, prev, &rrset);
710933707f3Ssthen 			continue;
711933707f3Ssthen 		}
7128b7325afSsthen 		/* Allowed list of types in the additional section */
7138b7325afSsthen 		if(env->cfg->harden_unknown_additional &&
7148b7325afSsthen 			!type_allowed_in_additional_section(rrset->type)) {
7158b7325afSsthen 			remove_rrset("normalize: removing irrelevant "
7168b7325afSsthen 				"RRset:", pkt, msg, prev, &rrset);
7178b7325afSsthen 			continue;
7188b7325afSsthen 		}
719933707f3Ssthen 		prev = rrset;
720933707f3Ssthen 		rrset = rrset->rrset_all_next;
721933707f3Ssthen 	}
722933707f3Ssthen 
723933707f3Ssthen 	return 1;
724933707f3Ssthen }
725933707f3Ssthen 
726933707f3Ssthen /**
727933707f3Ssthen  * Store potential poison in the cache (only if hardening disabled).
728933707f3Ssthen  * The rrset is stored in the cache but removed from the message.
729933707f3Ssthen  * So that it will be used for infrastructure purposes, but not be
730933707f3Ssthen  * returned to the client.
731933707f3Ssthen  * @param pkt: packet
732933707f3Ssthen  * @param msg: message parsed
733933707f3Ssthen  * @param env: environment with cache
734933707f3Ssthen  * @param rrset: to store.
735933707f3Ssthen  */
736933707f3Ssthen static void
7375d76a658Ssthen store_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct module_env* env,
738933707f3Ssthen 	struct rrset_parse* rrset)
739933707f3Ssthen {
740933707f3Ssthen 	struct ub_packed_rrset_key* k;
741933707f3Ssthen 	struct packed_rrset_data* d;
742933707f3Ssthen 	struct rrset_ref ref;
743229e174cSsthen 	time_t now = *env->now;
744933707f3Ssthen 
745933707f3Ssthen 	k = alloc_special_obtain(env->alloc);
746933707f3Ssthen 	if(!k)
747933707f3Ssthen 		return;
748933707f3Ssthen 	k->entry.data = NULL;
749933707f3Ssthen 	if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) {
750933707f3Ssthen 		alloc_special_release(env->alloc, k);
751933707f3Ssthen 		return;
752933707f3Ssthen 	}
753933707f3Ssthen 	d = (struct packed_rrset_data*)k->entry.data;
754933707f3Ssthen 	packed_rrset_ttl_add(d, now);
755933707f3Ssthen 	ref.key = k;
756933707f3Ssthen 	ref.id = k->id;
757933707f3Ssthen 	/*ignore ret: it was in the cache, ref updated */
758933707f3Ssthen 	(void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, now);
759933707f3Ssthen }
760933707f3Ssthen 
761933707f3Ssthen /**
762933707f3Ssthen  * Check if right hand name in NSEC is within zone
763191f22c6Ssthen  * @param pkt: the packet buffer for decompression.
764933707f3Ssthen  * @param rrset: the NSEC rrset
765933707f3Ssthen  * @param zonename: the zone name.
766933707f3Ssthen  * @return true if BAD.
767933707f3Ssthen  */
768191f22c6Ssthen static int sanitize_nsec_is_overreach(sldns_buffer* pkt,
769191f22c6Ssthen 	struct rrset_parse* rrset, uint8_t* zonename)
770933707f3Ssthen {
771933707f3Ssthen 	struct rr_parse* rr;
772933707f3Ssthen 	uint8_t* rhs;
773933707f3Ssthen 	size_t len;
774933707f3Ssthen 	log_assert(rrset->type == LDNS_RR_TYPE_NSEC);
775933707f3Ssthen 	for(rr = rrset->rr_first; rr; rr = rr->next) {
776191f22c6Ssthen 		size_t pos = sldns_buffer_position(pkt);
777191f22c6Ssthen 		size_t rhspos;
778933707f3Ssthen 		rhs = rr->ttl_data+4+2;
7795d76a658Ssthen 		len = sldns_read_uint16(rr->ttl_data+4);
780191f22c6Ssthen 		rhspos = rhs-sldns_buffer_begin(pkt);
781191f22c6Ssthen 		sldns_buffer_set_position(pkt, rhspos);
782191f22c6Ssthen 		if(pkt_dname_len(pkt) == 0) {
783191f22c6Ssthen 			/* malformed */
784191f22c6Ssthen 			sldns_buffer_set_position(pkt, pos);
785933707f3Ssthen 			return 1;
786933707f3Ssthen 		}
787191f22c6Ssthen 		if(sldns_buffer_position(pkt)-rhspos > len) {
788191f22c6Ssthen 			/* outside of rdata boundaries */
789191f22c6Ssthen 			sldns_buffer_set_position(pkt, pos);
790191f22c6Ssthen 			return 1;
791191f22c6Ssthen 		}
792191f22c6Ssthen 		sldns_buffer_set_position(pkt, pos);
793191f22c6Ssthen 		if(!pkt_sub(pkt, rhs, zonename)) {
794933707f3Ssthen 			/* overreaching */
795933707f3Ssthen 			return 1;
796933707f3Ssthen 		}
797933707f3Ssthen 	}
798933707f3Ssthen 	/* all NSEC RRs OK */
799933707f3Ssthen 	return 0;
800933707f3Ssthen }
801933707f3Ssthen 
802d896b962Ssthen /** Remove individual RRs, if the length is wrong. Returns true if the RRset
803d896b962Ssthen  * has been removed. */
804d896b962Ssthen static int
805d896b962Ssthen scrub_sanitize_rr_length(sldns_buffer* pkt, struct msg_parse* msg,
806d896b962Ssthen 	struct rrset_parse* prev, struct rrset_parse** rrset, int* added_ede,
807d896b962Ssthen 	struct module_qstate* qstate)
808d896b962Ssthen {
809d896b962Ssthen 	struct rr_parse* rr, *rr_prev = NULL;
810d896b962Ssthen 	for(rr = (*rrset)->rr_first; rr; rr = rr->next) {
811d896b962Ssthen 
812d896b962Ssthen 		/* Sanity check for length of records
813d896b962Ssthen 		 * An A record should be 6 bytes only
814d896b962Ssthen 		 * (2 bytes for length and 4 for IPv4 addr)*/
815d896b962Ssthen 		if((*rrset)->type == LDNS_RR_TYPE_A && rr->size != 6 ) {
816d896b962Ssthen 			if(!*added_ede) {
817d896b962Ssthen 				*added_ede = 1;
818d896b962Ssthen 				errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
819d896b962Ssthen 					LDNS_EDE_OTHER);
820d896b962Ssthen 			}
821d896b962Ssthen 			if(msgparse_rrset_remove_rr("sanitize: removing type A RR of inappropriate length:",
822d896b962Ssthen 				pkt, *rrset, rr_prev, rr, NULL, 0)) {
823d896b962Ssthen 				remove_rrset("sanitize: removing type A RRset of inappropriate length:",
824d896b962Ssthen 					pkt, msg, prev, rrset);
825d896b962Ssthen 				return 1;
826d896b962Ssthen 			}
827d896b962Ssthen 			continue;
828d896b962Ssthen 		}
829d896b962Ssthen 
830d896b962Ssthen 		/* Sanity check for length of records
831d896b962Ssthen 		 * An AAAA record should be 18 bytes only
832d896b962Ssthen 		 * (2 bytes for length and 16 for IPv6 addr)*/
833d896b962Ssthen 		if((*rrset)->type == LDNS_RR_TYPE_AAAA && rr->size != 18 ) {
834d896b962Ssthen 			if(!*added_ede) {
835d896b962Ssthen 				*added_ede = 1;
836d896b962Ssthen 				errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
837d896b962Ssthen 					LDNS_EDE_OTHER);
838d896b962Ssthen 			}
839d896b962Ssthen 			if(msgparse_rrset_remove_rr("sanitize: removing type AAAA RR of inappropriate length:",
840d896b962Ssthen 				pkt, *rrset, rr_prev, rr, NULL, 0)) {
841d896b962Ssthen 				remove_rrset("sanitize: removing type AAAA RRset of inappropriate length:",
842d896b962Ssthen 					pkt, msg, prev, rrset);
843d896b962Ssthen 				return 1;
844d896b962Ssthen 			}
845d896b962Ssthen 			continue;
846d896b962Ssthen 		}
847d896b962Ssthen 		rr_prev = rr;
848d896b962Ssthen 	}
849d896b962Ssthen 	return 0;
850d896b962Ssthen }
851d896b962Ssthen 
852933707f3Ssthen /**
853933707f3Ssthen  * Given a response event, remove suspect RRsets from the response.
854933707f3Ssthen  * "Suspect" rrsets are potentially poison. Note that this routine expects
855933707f3Ssthen  * the response to be in a "normalized" state -- that is, all "irrelevant"
856933707f3Ssthen  * RRsets have already been removed, CNAMEs are in order, etc.
857933707f3Ssthen  *
858933707f3Ssthen  * @param pkt: packet.
859933707f3Ssthen  * @param msg: msg to normalize.
860933707f3Ssthen  * @param qinfo: the question originally asked.
861933707f3Ssthen  * @param zonename: name of server zone.
862933707f3Ssthen  * @param env: module environment with config and cache.
863933707f3Ssthen  * @param ie: iterator environment with private address data.
864d896b962Ssthen  * @param qstate: for setting errinf for EDE error messages.
865933707f3Ssthen  * @return 0 on error.
866933707f3Ssthen  */
867933707f3Ssthen static int
8685d76a658Ssthen scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
869933707f3Ssthen 	struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
870d896b962Ssthen 	struct iter_env* ie, struct module_qstate* qstate)
871933707f3Ssthen {
872933707f3Ssthen 	int del_addi = 0; /* if additional-holding rrsets are deleted, we
873933707f3Ssthen 		do not trust the normalized additional-A-AAAA any more */
874d896b962Ssthen 	int added_rrlen_ede = 0;
875933707f3Ssthen 	struct rrset_parse* rrset, *prev;
876933707f3Ssthen 	prev = NULL;
877933707f3Ssthen 	rrset = msg->rrset_first;
878933707f3Ssthen 
879933707f3Ssthen 	/* the first DNAME is allowed to stay. It needs checking before
880933707f3Ssthen 	 * it can be used from the cache. After normalization, an initial
881933707f3Ssthen 	 * DNAME will have a correctly synthesized CNAME after it. */
882933707f3Ssthen 	if(rrset && rrset->type == LDNS_RR_TYPE_DNAME &&
883933707f3Ssthen 		rrset->section == LDNS_SECTION_ANSWER &&
884933707f3Ssthen 		pkt_strict_sub(pkt, qinfo->qname, rrset->dname) &&
885933707f3Ssthen 		pkt_sub(pkt, rrset->dname, zonename)) {
886933707f3Ssthen 		prev = rrset; /* DNAME allowed to stay in answer section */
887933707f3Ssthen 		rrset = rrset->rrset_all_next;
888933707f3Ssthen 	}
889933707f3Ssthen 
890933707f3Ssthen 	/* remove all records from the answer section that are
891933707f3Ssthen 	 * not the same domain name as the query domain name.
892933707f3Ssthen 	 * The answer section should contain rrsets with the same name
893933707f3Ssthen 	 * as the question. For DNAMEs a CNAME has been synthesized.
894933707f3Ssthen 	 * Wildcards have the query name in answer section.
895933707f3Ssthen 	 * ANY queries get query name in answer section.
896933707f3Ssthen 	 * Remainders of CNAME chains are cut off and resolved by iterator. */
897933707f3Ssthen 	while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
898933707f3Ssthen 		if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) {
899933707f3Ssthen 			if(has_additional(rrset->type)) del_addi = 1;
900933707f3Ssthen 			remove_rrset("sanitize: removing extraneous answer "
901933707f3Ssthen 				"RRset:", pkt, msg, prev, &rrset);
902933707f3Ssthen 			continue;
903933707f3Ssthen 		}
904933707f3Ssthen 		prev = rrset;
905933707f3Ssthen 		rrset = rrset->rrset_all_next;
906933707f3Ssthen 	}
907933707f3Ssthen 
908933707f3Ssthen 	/* At this point, we brutally remove ALL rrsets that aren't
909933707f3Ssthen 	 * children of the originating zone. The idea here is that,
910933707f3Ssthen 	 * as far as we know, the server that we contacted is ONLY
911933707f3Ssthen 	 * authoritative for the originating zone. It, of course, MAY
9124bfc71b0Ssthen 	 * be authoritative for any other zones, and of course, MAY
913933707f3Ssthen 	 * NOT be authoritative for some subdomains of the originating
914933707f3Ssthen 	 * zone. */
915933707f3Ssthen 	prev = NULL;
916933707f3Ssthen 	rrset = msg->rrset_first;
917933707f3Ssthen 	while(rrset) {
918933707f3Ssthen 
919d896b962Ssthen 		/* Sanity check for length of records */
920d896b962Ssthen 		if(rrset->type == LDNS_RR_TYPE_A ||
921d896b962Ssthen 			rrset->type == LDNS_RR_TYPE_AAAA) {
922d896b962Ssthen 			if(scrub_sanitize_rr_length(pkt, msg, prev, &rrset,
923d896b962Ssthen 				&added_rrlen_ede, qstate))
924d896b962Ssthen 				continue;
925d896b962Ssthen 		}
926d896b962Ssthen 
927933707f3Ssthen 		/* remove private addresses */
928933707f3Ssthen 		if( (rrset->type == LDNS_RR_TYPE_A ||
929229e174cSsthen 			rrset->type == LDNS_RR_TYPE_AAAA)) {
930933707f3Ssthen 
931933707f3Ssthen 			/* do not set servfail since this leads to too
932933707f3Ssthen 			 * many drops of other people using rfc1918 space */
933229e174cSsthen 			/* also do not remove entire rrset, unless all records
934229e174cSsthen 			 * in it are bad */
935229e174cSsthen 			if(priv_rrset_bad(ie->priv, pkt, rrset)) {
936229e174cSsthen 				remove_rrset(NULL, pkt, msg, prev, &rrset);
937933707f3Ssthen 				continue;
938933707f3Ssthen 			}
939229e174cSsthen 		}
940933707f3Ssthen 
941933707f3Ssthen 		/* skip DNAME records -- they will always be followed by a
942933707f3Ssthen 		 * synthesized CNAME, which will be relevant.
943933707f3Ssthen 		 * FIXME: should this do something differently with DNAME
944933707f3Ssthen 		 * rrsets NOT in Section.ANSWER? */
945933707f3Ssthen 		/* But since DNAME records are also subdomains of the zone,
946933707f3Ssthen 		 * same check can be used */
947933707f3Ssthen 
948933707f3Ssthen 		if(!pkt_sub(pkt, rrset->dname, zonename)) {
949933707f3Ssthen 			if(msg->an_rrsets == 0 &&
950933707f3Ssthen 				rrset->type == LDNS_RR_TYPE_NS &&
951933707f3Ssthen 				rrset->section == LDNS_SECTION_AUTHORITY &&
952933707f3Ssthen 				FLAGS_GET_RCODE(msg->flags) ==
953933707f3Ssthen 				LDNS_RCODE_NOERROR && !soa_in_auth(msg) &&
954933707f3Ssthen 				sub_of_pkt(pkt, zonename, rrset->dname)) {
955933707f3Ssthen 				/* noerror, nodata and this NS rrset is above
956933707f3Ssthen 				 * the zone. This is LAME!
957933707f3Ssthen 				 * Leave in the NS for lame classification. */
958933707f3Ssthen 				/* remove everything from the additional
959933707f3Ssthen 				 * (we dont want its glue that was approved
960933707f3Ssthen 				 * during the normalize action) */
961933707f3Ssthen 				del_addi = 1;
9623e38fc85Sbrad 			} else if(!env->cfg->harden_glue && (
9633e38fc85Sbrad 				rrset->type == LDNS_RR_TYPE_A ||
9643e38fc85Sbrad 				rrset->type == LDNS_RR_TYPE_AAAA)) {
965933707f3Ssthen 				/* store in cache! Since it is relevant
966933707f3Ssthen 				 * (from normalize) it will be picked up
967933707f3Ssthen 				 * from the cache to be used later */
968933707f3Ssthen 				store_rrset(pkt, msg, env, rrset);
969933707f3Ssthen 				remove_rrset("sanitize: storing potential "
970933707f3Ssthen 				"poison RRset:", pkt, msg, prev, &rrset);
971933707f3Ssthen 				continue;
972933707f3Ssthen 			} else {
973933707f3Ssthen 				if(has_additional(rrset->type)) del_addi = 1;
974933707f3Ssthen 				remove_rrset("sanitize: removing potential "
975933707f3Ssthen 				"poison RRset:", pkt, msg, prev, &rrset);
976933707f3Ssthen 				continue;
977933707f3Ssthen 			}
978933707f3Ssthen 		}
979933707f3Ssthen 		if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) {
980933707f3Ssthen 			remove_rrset("sanitize: removing potential "
981933707f3Ssthen 			"poison reference RRset:", pkt, msg, prev, &rrset);
982933707f3Ssthen 			continue;
983933707f3Ssthen 		}
984933707f3Ssthen 		/* check if right hand side of NSEC is within zone */
985933707f3Ssthen 		if(rrset->type == LDNS_RR_TYPE_NSEC &&
986191f22c6Ssthen 			sanitize_nsec_is_overreach(pkt, rrset, zonename)) {
987933707f3Ssthen 			remove_rrset("sanitize: removing overreaching NSEC "
988933707f3Ssthen 				"RRset:", pkt, msg, prev, &rrset);
989933707f3Ssthen 			continue;
990933707f3Ssthen 		}
991933707f3Ssthen 		prev = rrset;
992933707f3Ssthen 		rrset = rrset->rrset_all_next;
993933707f3Ssthen 	}
994933707f3Ssthen 	return 1;
995933707f3Ssthen }
996933707f3Ssthen 
997933707f3Ssthen int
9985d76a658Ssthen scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
999933707f3Ssthen 	struct query_info* qinfo, uint8_t* zonename, struct regional* region,
1000d896b962Ssthen 	struct module_env* env, struct module_qstate* qstate,
1001d896b962Ssthen 	struct iter_env* ie)
1002933707f3Ssthen {
1003933707f3Ssthen 	/* basic sanity checks */
1004933707f3Ssthen 	log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
1005933707f3Ssthen 		qinfo->qclass);
1006933707f3Ssthen 	if(msg->qdcount > 1)
1007933707f3Ssthen 		return 0;
1008933707f3Ssthen 	if( !(msg->flags&BIT_QR) )
1009933707f3Ssthen 		return 0;
1010933707f3Ssthen 	msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */
1011933707f3Ssthen 
1012933707f3Ssthen 	/* make sure that a query is echoed back when NOERROR or NXDOMAIN */
1013933707f3Ssthen 	/* this is not required for basic operation but is a forgery
1014933707f3Ssthen 	 * resistance (security) feature */
1015933707f3Ssthen 	if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR ||
1016933707f3Ssthen 		FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) &&
1017933707f3Ssthen 		msg->qdcount == 0)
1018933707f3Ssthen 		return 0;
1019933707f3Ssthen 
1020933707f3Ssthen 	/* if a query is echoed back, make sure it is correct. Otherwise,
1021933707f3Ssthen 	 * this may be not a reply to our query. */
1022933707f3Ssthen 	if(msg->qdcount == 1) {
1023933707f3Ssthen 		if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0)
1024933707f3Ssthen 			return 0;
1025933707f3Ssthen 		if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass)
1026933707f3Ssthen 			return 0;
1027933707f3Ssthen 	}
1028933707f3Ssthen 
1029933707f3Ssthen 	/* normalize the response, this cleans up the additional.  */
10308b7325afSsthen 	if(!scrub_normalize(pkt, msg, qinfo, region, env))
1031933707f3Ssthen 		return 0;
1032933707f3Ssthen 	/* delete all out-of-zone information */
1033d896b962Ssthen 	if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate))
1034933707f3Ssthen 		return 0;
1035933707f3Ssthen 	return 1;
1036933707f3Ssthen }
1037