/* Copyright (C) 2013 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ /** * \file * \author Victor Julien */ #include "suricata-common.h" #include "suricata.h" #include "debug.h" #include "decode.h" #include "flow-util.h" #include "threads.h" #include "util-print.h" #include "util-pool.h" #include "util-debug.h" #include "stream-tcp-private.h" #include "stream-tcp-reassemble.h" #include "stream-tcp.h" #include "stream.h" #include "app-layer-protos.h" #include "app-layer-parser.h" #include "util-spm.h" #include "util-unittest.h" #include "app-layer-dns-tcp.h" struct DNSTcpHeader_ { uint16_t len; uint16_t tx_id; uint16_t flags; uint16_t questions; uint16_t answer_rr; uint16_t authority_rr; uint16_t additional_rr; } __attribute__((__packed__)); typedef struct DNSTcpHeader_ DNSTcpHeader; /** \internal * \param input_len at least enough for the DNSTcpHeader */ static int DNSTCPRequestParseProbe(uint8_t *input, uint32_t input_len) { #ifdef DEBUG BUG_ON(input_len < sizeof(DNSTcpHeader)); #endif SCLogDebug("starting %u", input_len); DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input; if (ntohs(dns_tcp_header->len) < sizeof(DNSHeader)) { goto bad_data; } if (ntohs(dns_tcp_header->len) >= input_len) { goto insufficient_data; } input += 2; input_len -= 2; DNSHeader *dns_header = (DNSHeader *)input; uint16_t q; const uint8_t *data = input + sizeof(DNSHeader); for (q = 0; q < ntohs(dns_header->questions); q++) { uint16_t fqdn_offset = 0; if (input + input_len < data + 1) { SCLogDebug("input buffer too small for len field"); goto insufficient_data; } SCLogDebug("query length %u", *data); while (*data != 0) { if (*data > 63) { /** \todo set event?*/ goto bad_data; } uint8_t length = *data; data++; if (length > 0) { if (input + input_len < data + length) { SCLogDebug("input buffer too small for domain of len %u", length); goto insufficient_data; } //PrintRawDataFp(stdout, data, qry->length); if ((fqdn_offset + length + 1) < DNS_MAX_SIZE) { fqdn_offset += length; } else { /** \todo set event? */ goto bad_data; } } data += length; if (input + input_len < data + 1) { SCLogDebug("input buffer too small for new len"); goto insufficient_data; } SCLogDebug("qry length %u", *data); } if (fqdn_offset) { fqdn_offset--; } data++; if (input + input_len < data + sizeof(DNSQueryTrailer)) { SCLogDebug("input buffer too small for DNSQueryTrailer"); goto insufficient_data; } #ifdef DEBUG DNSQueryTrailer *trailer = (DNSQueryTrailer *)data; SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class)); #endif data += sizeof(DNSQueryTrailer); } SCReturnInt(1); insufficient_data: SCReturnInt(0); bad_data: SCReturnInt(-1); } static int BufferData(DNSState *dns_state, uint8_t *data, uint16_t len) { if (dns_state->buffer == NULL) { if (DNSCheckMemcap(0xffff, dns_state) < 0) return -1; /** \todo be smarter about this, like use a pool or several pools for * chunks of various sizes */ dns_state->buffer = SCMalloc(0xffff); if (dns_state->buffer == NULL) { return -1; } DNSIncrMemcap(0xffff, dns_state); } if ((uint32_t)len + (uint32_t)dns_state->offset > (uint32_t)dns_state->record_len) { SCLogDebug("oh my, we have more data than the max record size. What do we do. WHAT DO WE DOOOOO!"); #ifdef DEBUG BUG_ON(1); #endif len = dns_state->record_len - dns_state->offset; } memcpy(dns_state->buffer + dns_state->offset, data, len); dns_state->offset += len; return 0; } static void BufferReset(DNSState *dns_state) { dns_state->record_len = 0; dns_state->offset = 0; } static int DNSRequestParseData(Flow *f, DNSState *dns_state, const uint8_t *input, const uint32_t input_len) { DNSHeader *dns_header = (DNSHeader *)input; if (DNSValidateRequestHeader(dns_state, dns_header) < 0) goto bad_data; //SCLogInfo("ID %04x", ntohs(dns_header->tx_id)); uint16_t q; const uint8_t *data = input + sizeof(DNSHeader); //PrintRawDataFp(stdout, (uint8_t*)data, input_len - (data - input)); for (q = 0; q < ntohs(dns_header->questions); q++) { uint8_t fqdn[DNS_MAX_SIZE]; uint16_t fqdn_offset = 0; if (input + input_len < data + 1) { SCLogDebug("input buffer too small for DNSTcpQuery"); goto insufficient_data; } SCLogDebug("query length %u", *data); while (*data != 0) { if (*data > 63) { /** \todo set event?*/ goto insufficient_data; } uint8_t length = *data; data++; if (length > 0) { if (input + input_len < data + length) { SCLogDebug("input buffer too small for domain of len %u", length); goto insufficient_data; } //PrintRawDataFp(stdout, data, qry->length); if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) { memcpy(fqdn + fqdn_offset, data, length); fqdn_offset += length; fqdn[fqdn_offset++] = '.'; } else { /** \todo set event? */ goto insufficient_data; } } data += length; if (input + input_len < data + 1) { SCLogDebug("input buffer too small for DNSTcpQuery(2)"); goto insufficient_data; } SCLogDebug("qry length %u", *data); } if (fqdn_offset) { fqdn_offset--; } data++; if (input + input_len < data + sizeof(DNSQueryTrailer)) { SCLogDebug("input buffer too small for DNSQueryTrailer"); goto insufficient_data; } DNSQueryTrailer *trailer = (DNSQueryTrailer *)data; SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class)); data += sizeof(DNSQueryTrailer); /* store our data */ if (dns_state != NULL) { DNSStoreQueryInState(dns_state, fqdn, fqdn_offset, ntohs(trailer->type), ntohs(trailer->class), ntohs(dns_header->tx_id)); } } SCReturnInt(1); bad_data: insufficient_data: SCReturnInt(-1); } /** \internal * \brief Parse DNS request packet */ static int DNSTCPRequestParse(Flow *f, void *dstate, AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, void *local_data) { DNSState *dns_state = (DNSState *)dstate; SCLogDebug("starting %u", input_len); if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { SCReturnInt(1); } /** \todo remove this when PP is fixed to enforce ipproto */ if (f != NULL && f->proto != IPPROTO_TCP) SCReturnInt(-1); /* probably a rst/fin sending an eof */ if (input == NULL || input_len == 0) { goto insufficient_data; } next_record: /* if this is the beginning of a record, we need at least the header */ if (dns_state->offset == 0 && input_len < sizeof(DNSTcpHeader)) { SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader)); goto insufficient_data; } SCLogDebug("input_len %u offset %u record %u", input_len, dns_state->offset, dns_state->record_len); /* this is the first data of this record */ if (dns_state->offset == 0) { DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input; SCLogDebug("DNS %p", dns_tcp_header); if (ntohs(dns_tcp_header->len) < sizeof(DNSHeader)) { /* bogus len, doesn't fit even basic dns header */ goto bad_data; } else if (ntohs(dns_tcp_header->len) == (input_len-2)) { /* we have all data, so process w/o buffering */ if (DNSRequestParseData(f, dns_state, input+2, input_len-2) < 0) goto bad_data; } else if ((input_len-2) > ntohs(dns_tcp_header->len)) { /* we have all data, so process w/o buffering */ if (DNSRequestParseData(f, dns_state, input+2, ntohs(dns_tcp_header->len)) < 0) goto bad_data; /* treat the rest of the data as a (potential) new record */ input += ntohs(dns_tcp_header->len); input_len -= ntohs(dns_tcp_header->len); goto next_record; } else { /* not enough data, store record length and buffer */ dns_state->record_len = ntohs(dns_tcp_header->len); BufferData(dns_state, input+2, input_len-2); } } else if (input_len + dns_state->offset < dns_state->record_len) { /* we don't have the full record yet, buffer */ BufferData(dns_state, input, input_len); } else if (input_len > (uint32_t)(dns_state->record_len - dns_state->offset)) { /* more data than expected, we may have another record coming up */ uint16_t need = (dns_state->record_len - dns_state->offset); BufferData(dns_state, input, need); int r = DNSRequestParseData(f, dns_state, dns_state->buffer, dns_state->record_len); BufferReset(dns_state); if (r < 0) goto bad_data; /* treat the rest of the data as a (potential) new record */ input += need; input_len -= need; goto next_record; } else { /* implied exactly the amount of data we want * add current to buffer, then inspect buffer */ BufferData(dns_state, input, input_len); int r = DNSRequestParseData(f, dns_state, dns_state->buffer, dns_state->record_len); BufferReset(dns_state); if (r < 0) goto bad_data; } SCReturnInt(1); insufficient_data: SCReturnInt(-1); bad_data: SCReturnInt(-1); } static int DNSReponseParseData(Flow *f, DNSState *dns_state, const uint8_t *input, const uint32_t input_len) { DNSHeader *dns_header = (DNSHeader *)input; if (DNSValidateResponseHeader(dns_state, dns_header) < 0) goto bad_data; DNSTransaction *tx = NULL; int found = 0; if ((tx = DNSTransactionFindByTxId(dns_state, ntohs(dns_header->tx_id))) != NULL) found = 1; if (!found) { SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE"); DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE); } uint16_t q; const uint8_t *data = input + sizeof(DNSHeader); for (q = 0; q < ntohs(dns_header->questions); q++) { uint8_t fqdn[DNS_MAX_SIZE]; uint16_t fqdn_offset = 0; if (input + input_len < data + 1) { SCLogDebug("input buffer too small for len field"); goto insufficient_data; } SCLogDebug("qry length %u", *data); while (*data != 0) { uint8_t length = *data; data++; if (length > 0) { if (input + input_len < data + length) { SCLogDebug("input buffer too small for domain of len %u", length); goto insufficient_data; } //PrintRawDataFp(stdout, data, length); if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) { memcpy(fqdn + fqdn_offset, data, length); fqdn_offset += length; fqdn[fqdn_offset++] = '.'; } } data += length; if (input + input_len < data + 1) { SCLogDebug("input buffer too small for len field"); goto insufficient_data; } SCLogDebug("length %u", *data); } if (fqdn_offset) { fqdn_offset--; } data++; if (input + input_len < data + sizeof(DNSQueryTrailer)) { SCLogDebug("input buffer too small for DNSQueryTrailer"); goto insufficient_data; } #if DEBUG DNSQueryTrailer *trailer = (DNSQueryTrailer *)data; SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class)); #endif data += sizeof(DNSQueryTrailer); } for (q = 0; q < ntohs(dns_header->answer_rr); q++) { data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_ANSWER, input, input_len, data); if (data == NULL) { goto insufficient_data; } } //PrintRawDataFp(stdout, (uint8_t *)data, input_len - (data - input)); for (q = 0; q < ntohs(dns_header->authority_rr); q++) { data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_AUTHORITY, input, input_len, data); if (data == NULL) { goto insufficient_data; } } /* parse rcode, e.g. "noerror" or "nxdomain" */ uint8_t rcode = ntohs(dns_header->flags) & 0x0F; if (rcode <= DNS_RCODE_NOTZONE) { SCLogDebug("rcode %u", rcode); if (tx != NULL) tx->rcode = rcode; } else { /* this is not invalid, rcodes can be user defined */ SCLogDebug("unexpected DNS rcode %u", rcode); } if (ntohs(dns_header->flags) & 0x0080) { SCLogDebug("recursion desired"); if (tx != NULL) tx->recursion_desired = 1; } if (tx != NULL) { tx->replied = 1; } SCReturnInt(1); bad_data: insufficient_data: SCReturnInt(-1); } /** \internal * \brief DNS TCP record parser, entry function * * Parses a DNS TCP record and fills the DNS state * * As TCP records can be 64k we'll have to buffer the data. Streaming parsing * would have been _very_ tricky due to the way names are compressed in DNS * */ static int DNSTCPResponseParse(Flow *f, void *dstate, AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, void *local_data) { DNSState *dns_state = (DNSState *)dstate; if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { SCReturnInt(1); } /** \todo remove this when PP is fixed to enforce ipproto */ if (f != NULL && f->proto != IPPROTO_TCP) SCReturnInt(-1); /* probably a rst/fin sending an eof */ if (input == NULL || input_len == 0) { goto insufficient_data; } next_record: /* if this is the beginning of a record, we need at least the header */ if (dns_state->offset == 0 && input_len < sizeof(DNSTcpHeader)) { SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader)); goto insufficient_data; } SCLogDebug("input_len %u offset %u record %u", input_len, dns_state->offset, dns_state->record_len); /* this is the first data of this record */ if (dns_state->offset == 0) { DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input; SCLogDebug("DNS %p", dns_tcp_header); if (ntohs(dns_tcp_header->len) == (input_len-2)) { /* we have all data, so process w/o buffering */ if (DNSReponseParseData(f, dns_state, input+2, input_len-2) < 0) goto bad_data; } else if ((input_len-2) > ntohs(dns_tcp_header->len)) { /* we have all data, so process w/o buffering */ if (DNSReponseParseData(f, dns_state, input+2, ntohs(dns_tcp_header->len)) < 0) goto bad_data; /* treat the rest of the data as a (potential) new record */ input += ntohs(dns_tcp_header->len); input_len -= ntohs(dns_tcp_header->len); goto next_record; } else { /* not enough data, store record length and buffer */ dns_state->record_len = ntohs(dns_tcp_header->len); BufferData(dns_state, input+2, input_len-2); } } else if (input_len + dns_state->offset < dns_state->record_len) { /* we don't have the full record yet, buffer */ BufferData(dns_state, input, input_len); } else if (input_len > (uint32_t)(dns_state->record_len - dns_state->offset)) { /* more data than expected, we may have another record coming up */ uint16_t need = (dns_state->record_len - dns_state->offset); BufferData(dns_state, input, need); int r = DNSReponseParseData(f, dns_state, dns_state->buffer, dns_state->record_len); BufferReset(dns_state); if (r < 0) goto bad_data; /* treat the rest of the data as a (potential) new record */ input += need; input_len -= need; goto next_record; } else { /* implied exactly the amount of data we want * add current to buffer, then inspect buffer */ BufferData(dns_state, input, input_len); int r = DNSReponseParseData(f, dns_state, dns_state->buffer, dns_state->record_len); BufferReset(dns_state); if (r < 0) goto bad_data; } SCReturnInt(1); insufficient_data: SCReturnInt(-1); bad_data: SCReturnInt(-1); } static uint16_t DNSTcpProbingParser(uint8_t *input, uint32_t ilen, uint32_t *offset) { if (ilen == 0 || ilen < sizeof(DNSTcpHeader)) { SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader)); return ALPROTO_UNKNOWN; } DNSTcpHeader *dns_header = (DNSTcpHeader *)input; if (ntohs(dns_header->len) < sizeof(DNSHeader)) { /* length field bogus, won't even fit a minimal DNS header. */ return ALPROTO_FAILED; } else if (ntohs(dns_header->len) > ilen) { int r = DNSTCPRequestParseProbe(input, ilen); if (r == -1) { /* probing parser told us "bad data", so it's not * DNS */ return ALPROTO_FAILED; } else if (ilen > 512) { SCLogDebug("all the parser told us was not enough data, which is expected. Lets assume it's DNS"); return ALPROTO_DNS; } SCLogDebug("not yet enough info %u > %u", ntohs(dns_header->len), ilen); return ALPROTO_UNKNOWN; } int r = DNSTCPRequestParseProbe(input, ilen); if (r != 1) return ALPROTO_FAILED; SCLogDebug("ALPROTO_DNS"); return ALPROTO_DNS; } void RegisterDNSTCPParsers(void) { char *proto_name = "dns"; /** DNS */ if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { AppLayerProtoDetectRegisterProtocol(ALPROTO_DNS, proto_name); if (RunmodeIsUnittests()) { AppLayerProtoDetectPPRegister(IPPROTO_TCP, "53", ALPROTO_DNS, 0, sizeof(DNSTcpHeader), STREAM_TOSERVER, DNSTcpProbingParser); } else { int have_cfg = AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, proto_name, ALPROTO_DNS, 0, sizeof(DNSTcpHeader), DNSTcpProbingParser); /* if we have no config, we enable the default port 53 */ if (!have_cfg) { SCLogWarning(SC_ERR_DNS_CONFIG, "no DNS TCP config found, " "enabling DNS detection on " "port 53."); AppLayerProtoDetectPPRegister(IPPROTO_TCP, "53", ALPROTO_DNS, 0, sizeof(DNSTcpHeader), STREAM_TOSERVER, DNSTcpProbingParser); } } } else { SCLogInfo("Protocol detection and parser disabled for %s protocol.", proto_name); return; } if (AppLayerParserConfParserEnabled("tcp", proto_name)) { AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_DNS, STREAM_TOSERVER, DNSTCPRequestParse); AppLayerParserRegisterParser(IPPROTO_TCP , ALPROTO_DNS, STREAM_TOCLIENT, DNSTCPResponseParse); AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_DNS, DNSStateAlloc, DNSStateFree); AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_DNS, DNSStateTransactionFree); AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_DNS, DNSGetEvents); AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_DNS, DNSHasEvents); AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_DNS, DNSStateHasTxDetectState, DNSGetTxDetectState, DNSSetTxDetectState); AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_DNS, DNSGetTx); AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_DNS, DNSGetTxCnt); AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_DNS, DNSGetAlstateProgress); AppLayerParserRegisterGetStateProgressCompletionStatus(IPPROTO_TCP, ALPROTO_DNS, DNSGetAlstateProgressCompletionStatus); DNSAppLayerRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_DNS); } else { SCLogInfo("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name); } return; } /* UNITTESTS */ #ifdef UNITTESTS void DNSTCPParserRegisterTests(void) { // UtRegisterTest("DNSTCPParserTest01", DNSTCPParserTest01, 1); } #endif