1238106Sdes/* 2238106Sdes * iterator/iter_scrub.c - scrubbing, normalization, sanitization of DNS msgs. 3238106Sdes * 4238106Sdes * Copyright (c) 2007, NLnet Labs. All rights reserved. 5238106Sdes * 6238106Sdes * This software is open source. 7238106Sdes * 8238106Sdes * Redistribution and use in source and binary forms, with or without 9238106Sdes * modification, are permitted provided that the following conditions 10238106Sdes * are met: 11238106Sdes * 12238106Sdes * Redistributions of source code must retain the above copyright notice, 13238106Sdes * this list of conditions and the following disclaimer. 14238106Sdes * 15238106Sdes * Redistributions in binary form must reproduce the above copyright notice, 16238106Sdes * this list of conditions and the following disclaimer in the documentation 17238106Sdes * and/or other materials provided with the distribution. 18238106Sdes * 19238106Sdes * Neither the name of the NLNET LABS nor the names of its contributors may 20238106Sdes * be used to endorse or promote products derived from this software without 21238106Sdes * specific prior written permission. 22238106Sdes * 23238106Sdes * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24269257Sdes * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25269257Sdes * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26269257Sdes * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27269257Sdes * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28269257Sdes * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29269257Sdes * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30269257Sdes * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31269257Sdes * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32269257Sdes * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33269257Sdes * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34238106Sdes */ 35238106Sdes 36238106Sdes/** 37238106Sdes * \file 38238106Sdes * 39238106Sdes * This file has routine(s) for cleaning up incoming DNS messages from 40238106Sdes * possible useless or malicious junk in it. 41238106Sdes */ 42238106Sdes#include "config.h" 43238106Sdes#include "iterator/iter_scrub.h" 44238106Sdes#include "iterator/iterator.h" 45238106Sdes#include "iterator/iter_priv.h" 46238106Sdes#include "services/cache/rrset.h" 47238106Sdes#include "util/log.h" 48238106Sdes#include "util/net_help.h" 49238106Sdes#include "util/regional.h" 50238106Sdes#include "util/config_file.h" 51238106Sdes#include "util/module.h" 52238106Sdes#include "util/data/msgparse.h" 53238106Sdes#include "util/data/dname.h" 54238106Sdes#include "util/data/msgreply.h" 55238106Sdes#include "util/alloc.h" 56291767Sdes#include "sldns/sbuffer.h" 57238106Sdes 58238106Sdes/** RRset flag used during scrubbing. The RRset is OK. */ 59238106Sdes#define RRSET_SCRUB_OK 0x80 60238106Sdes 61238106Sdes/** remove rrset, update loop variables */ 62238106Sdesstatic void 63269257Sdesremove_rrset(const char* str, sldns_buffer* pkt, struct msg_parse* msg, 64238106Sdes struct rrset_parse* prev, struct rrset_parse** rrset) 65238106Sdes{ 66269257Sdes if(verbosity >= VERB_QUERY && str 67238106Sdes && (*rrset)->dname_len <= LDNS_MAX_DOMAINLEN) { 68238106Sdes uint8_t buf[LDNS_MAX_DOMAINLEN+1]; 69238106Sdes dname_pkt_copy(pkt, buf, (*rrset)->dname); 70238106Sdes log_nametypeclass(VERB_QUERY, str, buf, 71238106Sdes (*rrset)->type, ntohs((*rrset)->rrset_class)); 72238106Sdes } 73238106Sdes if(prev) 74238106Sdes prev->rrset_all_next = (*rrset)->rrset_all_next; 75238106Sdes else msg->rrset_first = (*rrset)->rrset_all_next; 76238106Sdes if(msg->rrset_last == *rrset) 77238106Sdes msg->rrset_last = prev; 78238106Sdes msg->rrset_count --; 79238106Sdes switch((*rrset)->section) { 80238106Sdes case LDNS_SECTION_ANSWER: msg->an_rrsets--; break; 81238106Sdes case LDNS_SECTION_AUTHORITY: msg->ns_rrsets--; break; 82238106Sdes case LDNS_SECTION_ADDITIONAL: msg->ar_rrsets--; break; 83238106Sdes default: log_assert(0); 84238106Sdes } 85238106Sdes msgparse_bucket_remove(msg, *rrset); 86238106Sdes *rrset = (*rrset)->rrset_all_next; 87238106Sdes} 88238106Sdes 89238106Sdes/** return true if rr type has additional names in it */ 90238106Sdesstatic int 91238106Sdeshas_additional(uint16_t t) 92238106Sdes{ 93238106Sdes switch(t) { 94238106Sdes case LDNS_RR_TYPE_MB: 95238106Sdes case LDNS_RR_TYPE_MD: 96238106Sdes case LDNS_RR_TYPE_MF: 97238106Sdes case LDNS_RR_TYPE_NS: 98238106Sdes case LDNS_RR_TYPE_MX: 99238106Sdes case LDNS_RR_TYPE_KX: 100238106Sdes case LDNS_RR_TYPE_SRV: 101238106Sdes return 1; 102238106Sdes case LDNS_RR_TYPE_NAPTR: 103238106Sdes /* TODO: NAPTR not supported, glue stripped off */ 104238106Sdes return 0; 105238106Sdes } 106238106Sdes return 0; 107238106Sdes} 108238106Sdes 109238106Sdes/** get additional name from rrset RR, return false if no name present */ 110238106Sdesstatic int 111238106Sdesget_additional_name(struct rrset_parse* rrset, struct rr_parse* rr, 112269257Sdes uint8_t** nm, size_t* nmlen, sldns_buffer* pkt) 113238106Sdes{ 114238106Sdes size_t offset = 0; 115238106Sdes size_t len, oldpos; 116238106Sdes switch(rrset->type) { 117238106Sdes case LDNS_RR_TYPE_MB: 118238106Sdes case LDNS_RR_TYPE_MD: 119238106Sdes case LDNS_RR_TYPE_MF: 120238106Sdes case LDNS_RR_TYPE_NS: 121238106Sdes offset = 0; 122238106Sdes break; 123238106Sdes case LDNS_RR_TYPE_MX: 124238106Sdes case LDNS_RR_TYPE_KX: 125238106Sdes offset = 2; 126238106Sdes break; 127238106Sdes case LDNS_RR_TYPE_SRV: 128238106Sdes offset = 6; 129238106Sdes break; 130238106Sdes case LDNS_RR_TYPE_NAPTR: 131238106Sdes /* TODO: NAPTR not supported, glue stripped off */ 132238106Sdes return 0; 133238106Sdes default: 134238106Sdes return 0; 135238106Sdes } 136269257Sdes len = sldns_read_uint16(rr->ttl_data+sizeof(uint32_t)); 137238106Sdes if(len < offset+1) 138238106Sdes return 0; /* rdata field too small */ 139238106Sdes *nm = rr->ttl_data+sizeof(uint32_t)+sizeof(uint16_t)+offset; 140269257Sdes oldpos = sldns_buffer_position(pkt); 141269257Sdes sldns_buffer_set_position(pkt, (size_t)(*nm - sldns_buffer_begin(pkt))); 142238106Sdes *nmlen = pkt_dname_len(pkt); 143269257Sdes sldns_buffer_set_position(pkt, oldpos); 144238106Sdes if(*nmlen == 0) 145238106Sdes return 0; 146238106Sdes return 1; 147238106Sdes} 148238106Sdes 149238106Sdes/** Place mark on rrsets in additional section they are OK */ 150238106Sdesstatic void 151269257Sdesmark_additional_rrset(sldns_buffer* pkt, struct msg_parse* msg, 152238106Sdes struct rrset_parse* rrset) 153238106Sdes{ 154238106Sdes /* Mark A and AAAA for NS as appropriate additional section info. */ 155238106Sdes uint8_t* nm = NULL; 156238106Sdes size_t nmlen = 0; 157238106Sdes struct rr_parse* rr; 158238106Sdes 159238106Sdes if(!has_additional(rrset->type)) 160238106Sdes return; 161238106Sdes for(rr = rrset->rr_first; rr; rr = rr->next) { 162238106Sdes if(get_additional_name(rrset, rr, &nm, &nmlen, pkt)) { 163238106Sdes /* mark A */ 164238106Sdes hashvalue_t h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_A, 165238106Sdes rrset->rrset_class, 0); 166238106Sdes struct rrset_parse* r = msgparse_hashtable_lookup( 167238106Sdes msg, pkt, h, 0, nm, nmlen, 168238106Sdes LDNS_RR_TYPE_A, rrset->rrset_class); 169238106Sdes if(r && r->section == LDNS_SECTION_ADDITIONAL) { 170238106Sdes r->flags |= RRSET_SCRUB_OK; 171238106Sdes } 172238106Sdes 173238106Sdes /* mark AAAA */ 174238106Sdes h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_AAAA, 175238106Sdes rrset->rrset_class, 0); 176238106Sdes r = msgparse_hashtable_lookup(msg, pkt, h, 0, nm, 177238106Sdes nmlen, LDNS_RR_TYPE_AAAA, rrset->rrset_class); 178238106Sdes if(r && r->section == LDNS_SECTION_ADDITIONAL) { 179238106Sdes r->flags |= RRSET_SCRUB_OK; 180238106Sdes } 181238106Sdes } 182238106Sdes } 183238106Sdes} 184238106Sdes 185238106Sdes/** Get target name of a CNAME */ 186238106Sdesstatic int 187238106Sdesparse_get_cname_target(struct rrset_parse* rrset, uint8_t** sname, 188238106Sdes size_t* snamelen) 189238106Sdes{ 190238106Sdes if(rrset->rr_count != 1) { 191238106Sdes struct rr_parse* sig; 192238106Sdes verbose(VERB_ALGO, "Found CNAME rrset with " 193238106Sdes "size > 1: %u", (unsigned)rrset->rr_count); 194238106Sdes /* use the first CNAME! */ 195238106Sdes rrset->rr_count = 1; 196238106Sdes rrset->size = rrset->rr_first->size; 197238106Sdes for(sig=rrset->rrsig_first; sig; sig=sig->next) 198238106Sdes rrset->size += sig->size; 199238106Sdes rrset->rr_last = rrset->rr_first; 200238106Sdes rrset->rr_first->next = NULL; 201238106Sdes } 202238106Sdes if(rrset->rr_first->size < sizeof(uint16_t)+1) 203238106Sdes return 0; /* CNAME rdata too small */ 204238106Sdes *sname = rrset->rr_first->ttl_data + sizeof(uint32_t) 205238106Sdes + sizeof(uint16_t); /* skip ttl, rdatalen */ 206238106Sdes *snamelen = rrset->rr_first->size - sizeof(uint16_t); 207238106Sdes return 1; 208238106Sdes} 209238106Sdes 210238106Sdes/** Synthesize CNAME from DNAME, false if too long */ 211238106Sdesstatic int 212238106Sdessynth_cname(uint8_t* qname, size_t qnamelen, struct rrset_parse* dname_rrset, 213269257Sdes uint8_t* alias, size_t* aliaslen, sldns_buffer* pkt) 214238106Sdes{ 215238106Sdes /* we already know that sname is a strict subdomain of DNAME owner */ 216238106Sdes uint8_t* dtarg = NULL; 217238106Sdes size_t dtarglen; 218238106Sdes if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen)) 219238106Sdes return 0; 220238106Sdes log_assert(qnamelen > dname_rrset->dname_len); 221238106Sdes /* DNAME from com. to net. with qname example.com. -> example.net. */ 222238106Sdes /* so: \3com\0 to \3net\0 and qname \7example\3com\0 */ 223238106Sdes *aliaslen = qnamelen + dtarglen - dname_rrset->dname_len; 224238106Sdes if(*aliaslen > LDNS_MAX_DOMAINLEN) 225238106Sdes return 0; /* should have been RCODE YXDOMAIN */ 226238106Sdes /* decompress dnames into buffer, we know it fits */ 227238106Sdes dname_pkt_copy(pkt, alias, qname); 228238106Sdes dname_pkt_copy(pkt, alias+(qnamelen-dname_rrset->dname_len), dtarg); 229238106Sdes return 1; 230238106Sdes} 231238106Sdes 232238106Sdes/** synthesize a CNAME rrset */ 233238106Sdesstatic struct rrset_parse* 234238106Sdessynth_cname_rrset(uint8_t** sname, size_t* snamelen, uint8_t* alias, 235238106Sdes size_t aliaslen, struct regional* region, struct msg_parse* msg, 236238106Sdes struct rrset_parse* rrset, struct rrset_parse* prev, 237269257Sdes struct rrset_parse* nx, sldns_buffer* pkt) 238238106Sdes{ 239238106Sdes struct rrset_parse* cn = (struct rrset_parse*)regional_alloc(region, 240238106Sdes sizeof(struct rrset_parse)); 241238106Sdes if(!cn) 242238106Sdes return NULL; 243238106Sdes memset(cn, 0, sizeof(*cn)); 244238106Sdes cn->rr_first = (struct rr_parse*)regional_alloc(region, 245238106Sdes sizeof(struct rr_parse)); 246238106Sdes if(!cn->rr_first) 247238106Sdes return NULL; 248238106Sdes cn->rr_last = cn->rr_first; 249238106Sdes /* CNAME from sname to alias */ 250238106Sdes cn->dname = (uint8_t*)regional_alloc(region, *snamelen); 251238106Sdes if(!cn->dname) 252238106Sdes return NULL; 253238106Sdes dname_pkt_copy(pkt, cn->dname, *sname); 254238106Sdes cn->dname_len = *snamelen; 255238106Sdes cn->type = LDNS_RR_TYPE_CNAME; 256238106Sdes cn->section = rrset->section; 257238106Sdes cn->rrset_class = rrset->rrset_class; 258238106Sdes cn->rr_count = 1; 259238106Sdes cn->size = sizeof(uint16_t) + aliaslen; 260238106Sdes cn->hash=pkt_hash_rrset(pkt, cn->dname, cn->type, cn->rrset_class, 0); 261238106Sdes /* allocate TTL + rdatalen + uncompressed dname */ 262238106Sdes memset(cn->rr_first, 0, sizeof(struct rr_parse)); 263238106Sdes cn->rr_first->outside_packet = 1; 264238106Sdes cn->rr_first->ttl_data = (uint8_t*)regional_alloc(region, 265238106Sdes sizeof(uint32_t)+sizeof(uint16_t)+aliaslen); 266238106Sdes if(!cn->rr_first->ttl_data) 267238106Sdes return NULL; 268269257Sdes sldns_write_uint32(cn->rr_first->ttl_data, 0); /* TTL = 0 */ 269269257Sdes sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen); 270238106Sdes memmove(cn->rr_first->ttl_data+6, alias, aliaslen); 271238106Sdes cn->rr_first->size = sizeof(uint16_t)+aliaslen; 272238106Sdes 273238106Sdes /* link it in */ 274238106Sdes cn->rrset_all_next = nx; 275238106Sdes if(prev) 276238106Sdes prev->rrset_all_next = cn; 277238106Sdes else msg->rrset_first = cn; 278238106Sdes if(nx == NULL) 279238106Sdes msg->rrset_last = cn; 280238106Sdes msg->rrset_count ++; 281238106Sdes msg->an_rrsets++; 282238106Sdes /* it is not inserted in the msg hashtable. */ 283238106Sdes 284238106Sdes *sname = cn->rr_first->ttl_data + sizeof(uint32_t)+sizeof(uint16_t); 285238106Sdes *snamelen = aliaslen; 286238106Sdes return cn; 287238106Sdes} 288238106Sdes 289238106Sdes/** check if DNAME applies to a name */ 290238106Sdesstatic int 291269257Sdespkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr) 292238106Sdes{ 293238106Sdes uint8_t buf1[LDNS_MAX_DOMAINLEN+1]; 294238106Sdes uint8_t buf2[LDNS_MAX_DOMAINLEN+1]; 295238106Sdes /* decompress names */ 296238106Sdes dname_pkt_copy(pkt, buf1, sname); 297238106Sdes dname_pkt_copy(pkt, buf2, dr); 298238106Sdes return dname_strict_subdomain_c(buf1, buf2); 299238106Sdes} 300238106Sdes 301238106Sdes/** check subdomain with decompression */ 302238106Sdesstatic int 303269257Sdespkt_sub(sldns_buffer* pkt, uint8_t* comprname, uint8_t* zone) 304238106Sdes{ 305238106Sdes uint8_t buf[LDNS_MAX_DOMAINLEN+1]; 306238106Sdes dname_pkt_copy(pkt, buf, comprname); 307238106Sdes return dname_subdomain_c(buf, zone); 308238106Sdes} 309238106Sdes 310238106Sdes/** check subdomain with decompression, compressed is parent */ 311238106Sdesstatic int 312269257Sdessub_of_pkt(sldns_buffer* pkt, uint8_t* zone, uint8_t* comprname) 313238106Sdes{ 314238106Sdes uint8_t buf[LDNS_MAX_DOMAINLEN+1]; 315238106Sdes dname_pkt_copy(pkt, buf, comprname); 316238106Sdes return dname_subdomain_c(zone, buf); 317238106Sdes} 318238106Sdes 319238106Sdes/** 320238106Sdes * This routine normalizes a response. This includes removing "irrelevant" 321238106Sdes * records from the answer and additional sections and (re)synthesizing 322238106Sdes * CNAMEs from DNAMEs, if present. 323238106Sdes * 324238106Sdes * @param pkt: packet. 325238106Sdes * @param msg: msg to normalize. 326238106Sdes * @param qinfo: original query. 327238106Sdes * @param region: where to allocate synthesized CNAMEs. 328238106Sdes * @return 0 on error. 329238106Sdes */ 330238106Sdesstatic int 331269257Sdesscrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, 332238106Sdes struct query_info* qinfo, struct regional* region) 333238106Sdes{ 334238106Sdes uint8_t* sname = qinfo->qname; 335238106Sdes size_t snamelen = qinfo->qname_len; 336238106Sdes struct rrset_parse* rrset, *prev, *nsset=NULL; 337238106Sdes 338238106Sdes if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && 339238106Sdes FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN) 340238106Sdes return 1; 341238106Sdes 342238106Sdes /* For the ANSWER section, remove all "irrelevant" records and add 343238106Sdes * synthesized CNAMEs from DNAMEs 344238106Sdes * This will strip out-of-order CNAMEs as well. */ 345238106Sdes 346238106Sdes /* walk through the parse packet rrset list, keep track of previous 347238106Sdes * for insert and delete ease, and examine every RRset */ 348238106Sdes prev = NULL; 349238106Sdes rrset = msg->rrset_first; 350238106Sdes while(rrset && rrset->section == LDNS_SECTION_ANSWER) { 351238106Sdes if(rrset->type == LDNS_RR_TYPE_DNAME && 352238106Sdes pkt_strict_sub(pkt, sname, rrset->dname)) { 353238106Sdes /* check if next rrset is correct CNAME. else, 354238106Sdes * synthesize a CNAME */ 355238106Sdes struct rrset_parse* nx = rrset->rrset_all_next; 356238106Sdes uint8_t alias[LDNS_MAX_DOMAINLEN+1]; 357238106Sdes size_t aliaslen = 0; 358238106Sdes if(rrset->rr_count != 1) { 359238106Sdes verbose(VERB_ALGO, "Found DNAME rrset with " 360238106Sdes "size > 1: %u", 361238106Sdes (unsigned)rrset->rr_count); 362238106Sdes return 0; 363238106Sdes } 364238106Sdes if(!synth_cname(sname, snamelen, rrset, alias, 365238106Sdes &aliaslen, pkt)) { 366238106Sdes verbose(VERB_ALGO, "synthesized CNAME " 367238106Sdes "too long"); 368238106Sdes return 0; 369238106Sdes } 370238106Sdes if(nx && nx->type == LDNS_RR_TYPE_CNAME && 371238106Sdes dname_pkt_compare(pkt, sname, nx->dname) == 0) { 372238106Sdes /* check next cname */ 373238106Sdes uint8_t* t = NULL; 374238106Sdes size_t tlen = 0; 375291767Sdes if(!parse_get_cname_target(nx, &t, &tlen)) 376238106Sdes return 0; 377238106Sdes if(dname_pkt_compare(pkt, alias, t) == 0) { 378238106Sdes /* it's OK and better capitalized */ 379238106Sdes prev = rrset; 380238106Sdes rrset = nx; 381238106Sdes continue; 382238106Sdes } 383238106Sdes /* synth ourselves */ 384238106Sdes } 385238106Sdes /* synth a CNAME rrset */ 386238106Sdes prev = synth_cname_rrset(&sname, &snamelen, alias, 387238106Sdes aliaslen, region, msg, rrset, rrset, nx, pkt); 388238106Sdes if(!prev) { 389238106Sdes log_err("out of memory synthesizing CNAME"); 390238106Sdes return 0; 391238106Sdes } 392238106Sdes /* FIXME: resolve the conflict between synthesized 393238106Sdes * CNAME ttls and the cache. */ 394238106Sdes rrset = nx; 395238106Sdes continue; 396238106Sdes 397238106Sdes } 398238106Sdes 399238106Sdes /* The only records in the ANSWER section not allowed to */ 400238106Sdes if(dname_pkt_compare(pkt, sname, rrset->dname) != 0) { 401238106Sdes remove_rrset("normalize: removing irrelevant RRset:", 402238106Sdes pkt, msg, prev, &rrset); 403238106Sdes continue; 404238106Sdes } 405238106Sdes 406238106Sdes /* Follow the CNAME chain. */ 407238106Sdes if(rrset->type == LDNS_RR_TYPE_CNAME) { 408294190Sdes struct rrset_parse* nx = rrset->rrset_all_next; 409238106Sdes uint8_t* oldsname = sname; 410294190Sdes /* see if the next one is a DNAME, if so, swap them */ 411294190Sdes if(nx && nx->section == LDNS_SECTION_ANSWER && 412294190Sdes nx->type == LDNS_RR_TYPE_DNAME && 413294190Sdes nx->rr_count == 1 && 414294190Sdes pkt_strict_sub(pkt, sname, nx->dname)) { 415294190Sdes /* there is a DNAME after this CNAME, it 416294190Sdes * is in the ANSWER section, and the DNAME 417294190Sdes * applies to the name we cover */ 418294190Sdes /* check if the alias of the DNAME equals 419294190Sdes * this CNAME */ 420294190Sdes uint8_t alias[LDNS_MAX_DOMAINLEN+1]; 421294190Sdes size_t aliaslen = 0; 422294190Sdes uint8_t* t = NULL; 423294190Sdes size_t tlen = 0; 424294190Sdes if(synth_cname(sname, snamelen, nx, alias, 425294190Sdes &aliaslen, pkt) && 426294190Sdes parse_get_cname_target(rrset, &t, &tlen) && 427294190Sdes dname_pkt_compare(pkt, alias, t) == 0) { 428294190Sdes /* the synthesized CNAME equals the 429294190Sdes * current CNAME. This CNAME is the 430294190Sdes * one that the DNAME creates, and this 431294190Sdes * CNAME is better capitalised */ 432294190Sdes verbose(VERB_ALGO, "normalize: re-order of DNAME and its CNAME"); 433294190Sdes if(prev) prev->rrset_all_next = nx; 434294190Sdes else msg->rrset_first = nx; 435294190Sdes if(nx->rrset_all_next == NULL) 436294190Sdes msg->rrset_last = rrset; 437294190Sdes rrset->rrset_all_next = 438294190Sdes nx->rrset_all_next; 439294190Sdes nx->rrset_all_next = rrset; 440294190Sdes prev = nx; 441294190Sdes } 442294190Sdes } 443294190Sdes 444294190Sdes /* move to next name in CNAME chain */ 445238106Sdes if(!parse_get_cname_target(rrset, &sname, &snamelen)) 446238106Sdes return 0; 447238106Sdes prev = rrset; 448238106Sdes rrset = rrset->rrset_all_next; 449238106Sdes /* in CNAME ANY response, can have data after CNAME */ 450238106Sdes if(qinfo->qtype == LDNS_RR_TYPE_ANY) { 451238106Sdes while(rrset && rrset->section == 452238106Sdes LDNS_SECTION_ANSWER && 453238106Sdes dname_pkt_compare(pkt, oldsname, 454238106Sdes rrset->dname) == 0) { 455238106Sdes prev = rrset; 456238106Sdes rrset = rrset->rrset_all_next; 457238106Sdes } 458238106Sdes } 459238106Sdes continue; 460238106Sdes } 461238106Sdes 462238106Sdes /* Otherwise, make sure that the RRset matches the qtype. */ 463238106Sdes if(qinfo->qtype != LDNS_RR_TYPE_ANY && 464238106Sdes qinfo->qtype != rrset->type) { 465238106Sdes remove_rrset("normalize: removing irrelevant RRset:", 466238106Sdes pkt, msg, prev, &rrset); 467238106Sdes continue; 468238106Sdes } 469238106Sdes 470238106Sdes /* Mark the additional names from relevant rrset as OK. */ 471238106Sdes /* only for RRsets that match the query name, other ones 472238106Sdes * will be removed by sanitize, so no additional for them */ 473238106Sdes if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) 474238106Sdes mark_additional_rrset(pkt, msg, rrset); 475238106Sdes 476238106Sdes prev = rrset; 477238106Sdes rrset = rrset->rrset_all_next; 478238106Sdes } 479238106Sdes 480238106Sdes /* Mark additional names from AUTHORITY */ 481238106Sdes while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) { 482238106Sdes if(rrset->type==LDNS_RR_TYPE_DNAME || 483238106Sdes rrset->type==LDNS_RR_TYPE_CNAME || 484238106Sdes rrset->type==LDNS_RR_TYPE_A || 485238106Sdes rrset->type==LDNS_RR_TYPE_AAAA) { 486238106Sdes remove_rrset("normalize: removing irrelevant " 487238106Sdes "RRset:", pkt, msg, prev, &rrset); 488238106Sdes continue; 489238106Sdes } 490238106Sdes /* only one NS set allowed in authority section */ 491238106Sdes if(rrset->type==LDNS_RR_TYPE_NS) { 492238106Sdes /* NS set must be pertinent to the query */ 493238106Sdes if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) { 494238106Sdes remove_rrset("normalize: removing irrelevant " 495238106Sdes "RRset:", pkt, msg, prev, &rrset); 496238106Sdes continue; 497238106Sdes } 498238106Sdes if(nsset == NULL) { 499238106Sdes nsset = rrset; 500238106Sdes } else { 501238106Sdes remove_rrset("normalize: removing irrelevant " 502238106Sdes "RRset:", pkt, msg, prev, &rrset); 503238106Sdes continue; 504238106Sdes } 505238106Sdes } 506238106Sdes mark_additional_rrset(pkt, msg, rrset); 507238106Sdes prev = rrset; 508238106Sdes rrset = rrset->rrset_all_next; 509238106Sdes } 510238106Sdes 511238106Sdes /* For each record in the additional section, remove it if it is an 512238106Sdes * address record and not in the collection of additional names 513238106Sdes * found in ANSWER and AUTHORITY. */ 514238106Sdes /* These records have not been marked OK previously */ 515238106Sdes while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) { 516238106Sdes /* FIXME: what about other types? */ 517238106Sdes if(rrset->type==LDNS_RR_TYPE_A || 518238106Sdes rrset->type==LDNS_RR_TYPE_AAAA) 519238106Sdes { 520238106Sdes if((rrset->flags & RRSET_SCRUB_OK)) { 521238106Sdes /* remove flag to clean up flags variable */ 522238106Sdes rrset->flags &= ~RRSET_SCRUB_OK; 523238106Sdes } else { 524238106Sdes remove_rrset("normalize: removing irrelevant " 525238106Sdes "RRset:", pkt, msg, prev, &rrset); 526238106Sdes continue; 527238106Sdes } 528238106Sdes } 529238106Sdes if(rrset->type==LDNS_RR_TYPE_DNAME || 530238106Sdes rrset->type==LDNS_RR_TYPE_CNAME || 531238106Sdes rrset->type==LDNS_RR_TYPE_NS) { 532238106Sdes remove_rrset("normalize: removing irrelevant " 533238106Sdes "RRset:", pkt, msg, prev, &rrset); 534238106Sdes continue; 535238106Sdes } 536238106Sdes prev = rrset; 537238106Sdes rrset = rrset->rrset_all_next; 538238106Sdes } 539238106Sdes 540238106Sdes return 1; 541238106Sdes} 542238106Sdes 543238106Sdes/** 544238106Sdes * Store potential poison in the cache (only if hardening disabled). 545238106Sdes * The rrset is stored in the cache but removed from the message. 546238106Sdes * So that it will be used for infrastructure purposes, but not be 547238106Sdes * returned to the client. 548238106Sdes * @param pkt: packet 549238106Sdes * @param msg: message parsed 550238106Sdes * @param env: environment with cache 551238106Sdes * @param rrset: to store. 552238106Sdes */ 553238106Sdesstatic void 554269257Sdesstore_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct module_env* env, 555238106Sdes struct rrset_parse* rrset) 556238106Sdes{ 557238106Sdes struct ub_packed_rrset_key* k; 558238106Sdes struct packed_rrset_data* d; 559238106Sdes struct rrset_ref ref; 560269257Sdes time_t now = *env->now; 561238106Sdes 562238106Sdes k = alloc_special_obtain(env->alloc); 563238106Sdes if(!k) 564238106Sdes return; 565238106Sdes k->entry.data = NULL; 566238106Sdes if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) { 567238106Sdes alloc_special_release(env->alloc, k); 568238106Sdes return; 569238106Sdes } 570238106Sdes d = (struct packed_rrset_data*)k->entry.data; 571238106Sdes packed_rrset_ttl_add(d, now); 572238106Sdes ref.key = k; 573238106Sdes ref.id = k->id; 574238106Sdes /*ignore ret: it was in the cache, ref updated */ 575238106Sdes (void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, now); 576238106Sdes} 577238106Sdes 578238106Sdes/** Check if there are SOA records in the authority section (negative) */ 579238106Sdesstatic int 580238106Sdessoa_in_auth(struct msg_parse* msg) 581238106Sdes{ 582238106Sdes struct rrset_parse* rrset; 583238106Sdes for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next) 584238106Sdes if(rrset->type == LDNS_RR_TYPE_SOA && 585238106Sdes rrset->section == LDNS_SECTION_AUTHORITY) 586238106Sdes return 1; 587238106Sdes return 0; 588238106Sdes} 589238106Sdes 590238106Sdes/** 591238106Sdes * Check if right hand name in NSEC is within zone 592238106Sdes * @param rrset: the NSEC rrset 593238106Sdes * @param zonename: the zone name. 594238106Sdes * @return true if BAD. 595238106Sdes */ 596238106Sdesstatic int sanitize_nsec_is_overreach(struct rrset_parse* rrset, 597238106Sdes uint8_t* zonename) 598238106Sdes{ 599238106Sdes struct rr_parse* rr; 600238106Sdes uint8_t* rhs; 601238106Sdes size_t len; 602238106Sdes log_assert(rrset->type == LDNS_RR_TYPE_NSEC); 603238106Sdes for(rr = rrset->rr_first; rr; rr = rr->next) { 604238106Sdes rhs = rr->ttl_data+4+2; 605269257Sdes len = sldns_read_uint16(rr->ttl_data+4); 606238106Sdes if(!dname_valid(rhs, len)) { 607238106Sdes /* malformed domain name in rdata */ 608238106Sdes return 1; 609238106Sdes } 610238106Sdes if(!dname_subdomain_c(rhs, zonename)) { 611238106Sdes /* overreaching */ 612238106Sdes return 1; 613238106Sdes } 614238106Sdes } 615238106Sdes /* all NSEC RRs OK */ 616238106Sdes return 0; 617238106Sdes} 618238106Sdes 619238106Sdes/** 620238106Sdes * Given a response event, remove suspect RRsets from the response. 621238106Sdes * "Suspect" rrsets are potentially poison. Note that this routine expects 622238106Sdes * the response to be in a "normalized" state -- that is, all "irrelevant" 623238106Sdes * RRsets have already been removed, CNAMEs are in order, etc. 624238106Sdes * 625238106Sdes * @param pkt: packet. 626238106Sdes * @param msg: msg to normalize. 627238106Sdes * @param qinfo: the question originally asked. 628238106Sdes * @param zonename: name of server zone. 629238106Sdes * @param env: module environment with config and cache. 630238106Sdes * @param ie: iterator environment with private address data. 631238106Sdes * @return 0 on error. 632238106Sdes */ 633238106Sdesstatic int 634269257Sdesscrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg, 635238106Sdes struct query_info* qinfo, uint8_t* zonename, struct module_env* env, 636238106Sdes struct iter_env* ie) 637238106Sdes{ 638238106Sdes int del_addi = 0; /* if additional-holding rrsets are deleted, we 639238106Sdes do not trust the normalized additional-A-AAAA any more */ 640238106Sdes struct rrset_parse* rrset, *prev; 641238106Sdes prev = NULL; 642238106Sdes rrset = msg->rrset_first; 643238106Sdes 644238106Sdes /* the first DNAME is allowed to stay. It needs checking before 645238106Sdes * it can be used from the cache. After normalization, an initial 646238106Sdes * DNAME will have a correctly synthesized CNAME after it. */ 647238106Sdes if(rrset && rrset->type == LDNS_RR_TYPE_DNAME && 648238106Sdes rrset->section == LDNS_SECTION_ANSWER && 649238106Sdes pkt_strict_sub(pkt, qinfo->qname, rrset->dname) && 650238106Sdes pkt_sub(pkt, rrset->dname, zonename)) { 651238106Sdes prev = rrset; /* DNAME allowed to stay in answer section */ 652238106Sdes rrset = rrset->rrset_all_next; 653238106Sdes } 654238106Sdes 655238106Sdes /* remove all records from the answer section that are 656238106Sdes * not the same domain name as the query domain name. 657238106Sdes * The answer section should contain rrsets with the same name 658238106Sdes * as the question. For DNAMEs a CNAME has been synthesized. 659238106Sdes * Wildcards have the query name in answer section. 660238106Sdes * ANY queries get query name in answer section. 661238106Sdes * Remainders of CNAME chains are cut off and resolved by iterator. */ 662238106Sdes while(rrset && rrset->section == LDNS_SECTION_ANSWER) { 663238106Sdes if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) { 664238106Sdes if(has_additional(rrset->type)) del_addi = 1; 665238106Sdes remove_rrset("sanitize: removing extraneous answer " 666238106Sdes "RRset:", pkt, msg, prev, &rrset); 667238106Sdes continue; 668238106Sdes } 669238106Sdes prev = rrset; 670238106Sdes rrset = rrset->rrset_all_next; 671238106Sdes } 672238106Sdes 673238106Sdes /* At this point, we brutally remove ALL rrsets that aren't 674238106Sdes * children of the originating zone. The idea here is that, 675238106Sdes * as far as we know, the server that we contacted is ONLY 676238106Sdes * authoritative for the originating zone. It, of course, MAY 677294190Sdes * be authoritative for any other zones, and of course, MAY 678238106Sdes * NOT be authoritative for some subdomains of the originating 679238106Sdes * zone. */ 680238106Sdes prev = NULL; 681238106Sdes rrset = msg->rrset_first; 682238106Sdes while(rrset) { 683238106Sdes 684238106Sdes /* remove private addresses */ 685238106Sdes if( (rrset->type == LDNS_RR_TYPE_A || 686269257Sdes rrset->type == LDNS_RR_TYPE_AAAA)) { 687238106Sdes 688238106Sdes /* do not set servfail since this leads to too 689238106Sdes * many drops of other people using rfc1918 space */ 690269257Sdes /* also do not remove entire rrset, unless all records 691269257Sdes * in it are bad */ 692269257Sdes if(priv_rrset_bad(ie->priv, pkt, rrset)) { 693269257Sdes remove_rrset(NULL, pkt, msg, prev, &rrset); 694269257Sdes continue; 695269257Sdes } 696238106Sdes } 697238106Sdes 698238106Sdes /* skip DNAME records -- they will always be followed by a 699238106Sdes * synthesized CNAME, which will be relevant. 700238106Sdes * FIXME: should this do something differently with DNAME 701238106Sdes * rrsets NOT in Section.ANSWER? */ 702238106Sdes /* But since DNAME records are also subdomains of the zone, 703238106Sdes * same check can be used */ 704238106Sdes 705238106Sdes if(!pkt_sub(pkt, rrset->dname, zonename)) { 706238106Sdes if(msg->an_rrsets == 0 && 707238106Sdes rrset->type == LDNS_RR_TYPE_NS && 708238106Sdes rrset->section == LDNS_SECTION_AUTHORITY && 709238106Sdes FLAGS_GET_RCODE(msg->flags) == 710238106Sdes LDNS_RCODE_NOERROR && !soa_in_auth(msg) && 711238106Sdes sub_of_pkt(pkt, zonename, rrset->dname)) { 712238106Sdes /* noerror, nodata and this NS rrset is above 713238106Sdes * the zone. This is LAME! 714238106Sdes * Leave in the NS for lame classification. */ 715238106Sdes /* remove everything from the additional 716238106Sdes * (we dont want its glue that was approved 717238106Sdes * during the normalize action) */ 718238106Sdes del_addi = 1; 719285206Sdes } else if(!env->cfg->harden_glue && ( 720285206Sdes rrset->type == LDNS_RR_TYPE_A || 721285206Sdes rrset->type == LDNS_RR_TYPE_AAAA)) { 722238106Sdes /* store in cache! Since it is relevant 723238106Sdes * (from normalize) it will be picked up 724238106Sdes * from the cache to be used later */ 725238106Sdes store_rrset(pkt, msg, env, rrset); 726238106Sdes remove_rrset("sanitize: storing potential " 727238106Sdes "poison RRset:", pkt, msg, prev, &rrset); 728238106Sdes continue; 729238106Sdes } else { 730238106Sdes if(has_additional(rrset->type)) del_addi = 1; 731238106Sdes remove_rrset("sanitize: removing potential " 732238106Sdes "poison RRset:", pkt, msg, prev, &rrset); 733238106Sdes continue; 734238106Sdes } 735238106Sdes } 736238106Sdes if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) { 737238106Sdes remove_rrset("sanitize: removing potential " 738238106Sdes "poison reference RRset:", pkt, msg, prev, &rrset); 739238106Sdes continue; 740238106Sdes } 741238106Sdes /* check if right hand side of NSEC is within zone */ 742238106Sdes if(rrset->type == LDNS_RR_TYPE_NSEC && 743238106Sdes sanitize_nsec_is_overreach(rrset, zonename)) { 744238106Sdes remove_rrset("sanitize: removing overreaching NSEC " 745238106Sdes "RRset:", pkt, msg, prev, &rrset); 746238106Sdes continue; 747238106Sdes } 748238106Sdes prev = rrset; 749238106Sdes rrset = rrset->rrset_all_next; 750238106Sdes } 751238106Sdes return 1; 752238106Sdes} 753238106Sdes 754238106Sdesint 755269257Sdesscrub_message(sldns_buffer* pkt, struct msg_parse* msg, 756238106Sdes struct query_info* qinfo, uint8_t* zonename, struct regional* region, 757238106Sdes struct module_env* env, struct iter_env* ie) 758238106Sdes{ 759238106Sdes /* basic sanity checks */ 760238106Sdes log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS, 761238106Sdes qinfo->qclass); 762238106Sdes if(msg->qdcount > 1) 763238106Sdes return 0; 764238106Sdes if( !(msg->flags&BIT_QR) ) 765238106Sdes return 0; 766238106Sdes msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */ 767238106Sdes 768238106Sdes /* make sure that a query is echoed back when NOERROR or NXDOMAIN */ 769238106Sdes /* this is not required for basic operation but is a forgery 770238106Sdes * resistance (security) feature */ 771238106Sdes if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR || 772238106Sdes FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) && 773238106Sdes msg->qdcount == 0) 774238106Sdes return 0; 775238106Sdes 776238106Sdes /* if a query is echoed back, make sure it is correct. Otherwise, 777238106Sdes * this may be not a reply to our query. */ 778238106Sdes if(msg->qdcount == 1) { 779238106Sdes if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0) 780238106Sdes return 0; 781238106Sdes if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass) 782238106Sdes return 0; 783238106Sdes } 784238106Sdes 785238106Sdes /* normalize the response, this cleans up the additional. */ 786238106Sdes if(!scrub_normalize(pkt, msg, qinfo, region)) 787238106Sdes return 0; 788238106Sdes /* delete all out-of-zone information */ 789238106Sdes if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie)) 790238106Sdes return 0; 791238106Sdes return 1; 792238106Sdes} 793