You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/src/app-layer-dns-tcp.c

638 lines
21 KiB
C

/* 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 <victor@inliniac.net>
*/
#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) {
/** \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;
}
}
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, AppLayerParserResult *output)
{
DNSState *dns_state = (DNSState *)dstate;
SCLogDebug("starting %u", input_len);
/** \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_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;
TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
if (tx->tx_id == ntohs(dns_header->tx_id)) {
found = 1;
break;
}
}
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;
}
length = *data;
SCLogDebug("length %u", length);
}
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;
}
}
if (!found) {
SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
}
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, AppLayerParserResult *output)
{
DNSState *dns_state = (DNSState *)dstate;
/** \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_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_TCP;
}
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_TCP");
return ALPROTO_DNS_TCP;
}
void RegisterDNSTCPParsers(void) {
char *proto_name = "dnstcp";
/** DNS */
if (AppLayerProtoDetectionEnabled(proto_name)) {
if (RunmodeIsUnittests()) {
AppLayerRegisterProbingParser(&alp_proto_ctx,
IPPROTO_TCP,
"53",
proto_name,
ALPROTO_DNS_TCP,
0, sizeof(DNSTcpHeader),
STREAM_TOSERVER,
DNSTcpProbingParser);
} else {
AppLayerParseProbingParserPorts(proto_name, ALPROTO_DNS_TCP,
0, sizeof(DNSTcpHeader),
DNSTcpProbingParser);
}
} else {
SCLogInfo("Protocol detection and parser disabled for %s protocol.",
proto_name);
return;
}
if (AppLayerParserEnabled(proto_name)) {
AppLayerRegisterProto(proto_name, ALPROTO_DNS_TCP, STREAM_TOSERVER,
DNSTCPRequestParse);
AppLayerRegisterProto(proto_name, ALPROTO_DNS_TCP, STREAM_TOCLIENT,
DNSTCPResponseParse);
AppLayerRegisterStateFuncs(ALPROTO_DNS_TCP, DNSStateAlloc,
DNSStateFree);
AppLayerRegisterTxFreeFunc(ALPROTO_DNS_TCP,
DNSStateTransactionFree);
AppLayerRegisterGetEventsFunc(ALPROTO_DNS_TCP, DNSGetEvents);
AppLayerRegisterHasEventsFunc(ALPROTO_DNS_TCP, DNSHasEvents);
AppLayerRegisterGetTx(ALPROTO_DNS_TCP,
DNSGetTx);
AppLayerRegisterGetTxCnt(ALPROTO_DNS_TCP,
DNSGetTxCnt);
AppLayerRegisterGetAlstateProgressFunc(ALPROTO_DNS_TCP,
DNSGetAlstateProgress);
AppLayerRegisterGetAlstateProgressCompletionStatus(ALPROTO_DNS_TCP,
DNSGetAlstateProgressCompletionStatus);
DNSAppLayerRegisterGetEventInfo(ALPROTO_DNS_TCP);
} 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