/* * Copyright (c) 2008-2024 OARC, Inc. * Copyright (c) 2007-2008, Internet Systems Consortium, Inc. * Copyright (c) 2003-2007, The Measurement Factory, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "dns_protocol.h" #include "dns_message.h" #include "pcap_layers/byteorder.h" #include "xmalloc.h" #include #include #include #define DNS_MSG_HDR_SZ 12 #define RFC1035_MAXLABELSZ 63 static int rfc1035NameUnpack(const u_char* buf, size_t sz, off_t* off, char* name, int ns) { off_t no = 0; unsigned char c; size_t len; /* * loop_detect[] tracks which position in the DNS message it has * jumped to so it can't jump to the same twice, aka loop */ static unsigned char loop_detect[0x3FFF] = { 0 }; if (ns <= 0) return 4; /* probably compression loop */ do { if ((*off) >= sz) break; c = *(buf + (*off)); if (c > 191) { /* blasted compression */ int rc; unsigned short s; off_t ptr, loop_ptr; s = nptohs(buf + (*off)); (*off) += sizeof(s); /* Sanity check */ if ((*off) >= sz) return 1; /* message too short */ ptr = s & 0x3FFF; /* Make sure the pointer is inside this message */ if (ptr >= sz) return 2; /* bad compression ptr */ if (ptr < DNS_MSG_HDR_SZ) return 2; /* bad compression ptr */ if (loop_detect[ptr]) return 4; /* compression loop */ loop_detect[(loop_ptr = ptr)] = 1; rc = rfc1035NameUnpack(buf, sz, &ptr, name + no, ns - no); loop_detect[loop_ptr] = 0; return rc; } else if (c > RFC1035_MAXLABELSZ) { /* * "(The 10 and 01 combinations are reserved for future use.)" */ return 3; /* reserved label/compression flags */ } else { (*off)++; len = (size_t)c; if (len == 0) break; if (len > (ns - 1)) len = ns - 1; if ((*off) + len > sz) return 4; /* message is too short */ if (no + len + 1 > ns) return 5; /* qname would overflow name buffer */ memcpy(name + no, buf + (*off), len); (*off) += len; no += len; *(name + (no++)) = '.'; } } while (c > 0); if (no > 0) *(name + no - 1) = '\0'; /* make sure we didn't allow someone to overflow the name buffer */ assert(no <= ns); return 0; } static int rfc1035NameSkip(const u_char* buf, size_t sz, off_t* off) { unsigned char c; size_t len; /* * loop_detect[] tracks which position in the DNS message it has * jumped to so it can't jump to the same twice, aka loop */ static unsigned char loop_detect[0x3FFF] = { 0 }; do { if ((*off) >= sz) break; c = *(buf + (*off)); if (c > 191) { /* blasted compression */ int rc; unsigned short s; off_t ptr, loop_ptr; s = nptohs(buf + (*off)); (*off) += sizeof(s); /* Sanity check */ if ((*off) >= sz) return 1; /* message too short */ ptr = s & 0x3FFF; /* Make sure the pointer is inside this message */ if (ptr >= sz) return 2; /* bad compression ptr */ if (ptr < DNS_MSG_HDR_SZ) return 2; /* bad compression ptr */ if (loop_detect[ptr]) return 4; /* compression loop */ loop_detect[(loop_ptr = ptr)] = 1; rc = rfc1035NameSkip(buf, sz, &ptr); loop_detect[loop_ptr] = 0; return rc; } else if (c > RFC1035_MAXLABELSZ) { /* * "(The 10 and 01 combinations are reserved for future use.)" */ return 3; /* reserved label/compression flags */ } else { (*off)++; len = (size_t)c; if (len == 0) break; if ((*off) + len > sz) return 4; /* message is too short */ (*off) += len; } } while (c > 0); return 0; } static off_t grok_question(const u_char* buf, int len, off_t offset, char* qname, unsigned short* qtype, unsigned short* qclass) { char* t; int x; x = rfc1035NameUnpack(buf, len, &offset, qname, MAX_QNAME_SZ); if (0 != x) return 0; if ('\0' == *qname) { *qname = '.'; *(qname + 1) = 0; } /* XXX remove special characters from QNAME */ while ((t = strchr(qname, '\n'))) *t = ' '; while ((t = strchr(qname, '\r'))) *t = ' '; for (t = qname; *t; t++) *t = tolower(*t); if (offset + 4 > len) return 0; *qtype = nptohs(buf + offset); *qclass = nptohs(buf + offset + 2); offset += 4; return offset; } static off_t skip_question(const u_char* buf, int len, off_t offset) { if (rfc1035NameSkip(buf, len, &offset)) return 0; if (offset + 4 > len) return 0; offset += 4; return offset; } #define EDNS0_TYPE_NSID 3 #define EDNS0_TYPE_ECS 8 #define EDNS0_TYPE_COOKIE 10 #define EDNS0_TYPE_EXTENDED_ERROR 15 static void process_edns0_options(const u_char* buf, int len, struct dns_message* m) { unsigned short edns0_type; unsigned short edns0_len; off_t offset = 0; while (len >= 4) { edns0_type = nptohs(buf + offset); edns0_len = nptohs(buf + offset + 2); if (len < 4 + edns0_len) break; switch (edns0_type) { case EDNS0_TYPE_COOKIE: if (m->edns.option.cookie) break; if (edns0_len == 8) { m->edns.option.cookie = 1; m->edns.cookie.client = buf + offset + 4; } else if (edns0_len >= 16 && edns0_len <= 40) { m->edns.option.cookie = 1; m->edns.cookie.client = buf + offset + 4; m->edns.cookie.server = m->edns.cookie.client + 8; m->edns.cookie.server_len = edns0_len - 8; } break; case EDNS0_TYPE_NSID: if (m->edns.option.nsid) break; m->edns.option.nsid = 1; if (edns0_len) { m->edns.nsid.data = buf + offset + 4; m->edns.nsid.len = edns0_len; } break; case EDNS0_TYPE_ECS: if (m->edns.option.ecs || edns0_len < 4) break; m->edns.option.ecs = 1; m->edns.ecs.family = nptohs(buf + offset + 4); m->edns.ecs.source_prefix = *(buf + offset + 6); m->edns.ecs.scope_prefix = *(buf + offset + 7); if (edns0_len > 4) { m->edns.ecs.address = buf + offset + 8; m->edns.ecs.len = edns0_len - 4; } break; case EDNS0_TYPE_EXTENDED_ERROR: if (m->edns.option.ede || edns0_len < 2) break; m->edns.option.ede = 1; m->edns.ede.code = nptohs(buf + offset + 4); if (edns0_len > 2) { m->edns.ede.text = buf + offset + 6; m->edns.ede.len = edns0_len - 2; } break; } offset += 4 + edns0_len; len -= 4 + edns0_len; } } int dns_protocol_parse_edns_options = 0; static off_t grok_additional_for_opt_rr(const u_char* buf, int len, off_t offset, dns_message* m) { unsigned short us; /* * OPT RR for EDNS0 MUST be 0 (root domain), so if the first byte of * the name is anything it can't be a valid EDNS0 record. */ if (*(buf + offset)) { if (rfc1035NameSkip(buf, len, &offset)) return 0; if (offset + 10 > len) return 0; } else { offset++; if (offset + 10 > len) return 0; if (nptohs(buf + offset) == T_OPT && !m->edns.found) { m->edns.found = 1; m->edns.bufsiz = nptohs(buf + offset + 2); m->edns.version = *(buf + offset + 5); us = nptohs(buf + offset + 6); m->edns.DO = (us >> 15) & 0x01; /* RFC 3225 */ us = nptohs(buf + offset + 8); // rd len offset += 10; if (offset + us > len) return 0; if (dns_protocol_parse_edns_options && !m->edns.version && us > 0) process_edns0_options(buf + offset, us, m); offset += us; return offset; } } /* get rdlength */ us = nptohs(buf + offset + 8); offset += 10; if (offset + us > len) return 0; offset += us; return offset; } static off_t skip_rr(const u_char* buf, int len, off_t offset) { if (rfc1035NameSkip(buf, len, &offset)) return 0; if (offset + 10 > len) return 0; unsigned short us = nptohs(buf + offset + 8); offset += 10; if (offset + us > len) return 0; offset += us; return offset; } int dns_protocol_parse_edns = 0; int dns_protocol_handler(const u_char* buf, int len, void* udata) { transport_message* tm = udata; unsigned short us; off_t offset, new_offset; int qdcount, ancount, nscount, arcount; dns_message m; memset(&m, 0, sizeof(dns_message)); m.tm = tm; m.msglen = len; if (len < DNS_MSG_HDR_SZ) { m.malformed = 1; return 0; } m.id = nptohs(buf); us = nptohs(buf + 2); m.qr = (us >> 15) & 0x01; m.opcode = (us >> 11) & 0x0F; m.aa = (us >> 10) & 0x01; m.tc = (us >> 9) & 0x01; m.rd = (us >> 8) & 0x01; /* m.ra = (us >> 7) & 0x01; */ /* m.z = (us >> 6) & 0x01; */ m.ad = (us >> 5) & 0x01; /* m.cd = (us >> 4) & 0x01; */ m.rcode = us & 0x0F; qdcount = nptohs(buf + 4); ancount = nptohs(buf + 6); nscount = nptohs(buf + 8); arcount = nptohs(buf + 10); offset = DNS_MSG_HDR_SZ; /* * Grab the first question */ if (qdcount > 0 && offset < len) { if (!(new_offset = grok_question(buf, len, offset, m.qname, &m.qtype, &m.qclass))) { m.malformed = 1; return 0; } offset = new_offset; qdcount--; } if (!dns_protocol_parse_edns) goto handle_m; assert(offset <= len); /* * Gobble up subsequent questions, if any */ while (qdcount > 0 && offset < len) { if (!(new_offset = skip_question(buf, len, offset))) { goto handle_m; } offset = new_offset; qdcount--; } assert(offset <= len); /* * Gobble up answers, if any */ while (ancount > 0 && offset < len) { if (!(new_offset = skip_rr(buf, len, offset))) { goto handle_m; } offset = new_offset; ancount--; } assert(offset <= len); /* * Gobble up authorities, if any */ while (nscount > 0 && offset < len) { if (!(new_offset = skip_rr(buf, len, offset))) { goto handle_m; } offset = new_offset; nscount--; } assert(offset <= len); /* * Process additional */ while (arcount > 0 && offset < len) { if (!(new_offset = grok_additional_for_opt_rr(buf, len, offset, &m))) { goto handle_m; } offset = new_offset; arcount--; } handle_m: assert(offset <= len); dns_message_handle(&m); return 0; }