/* Copyright (C) 2015 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 * * DNP3 protocol implementation */ #include "suricata-common.h" #include "suricata.h" #include "stream.h" #include "util-byte.h" #include "util-unittest.h" #include "util-hashlist.h" #include "util-print.h" #include "util-spm-bs.h" #include "util-enum.h" #include "app-layer.h" #include "app-layer-protos.h" #include "app-layer-parser.h" #include "app-layer-detect-proto.h" #include "app-layer-dnp3.h" #include "app-layer-dnp3-objects.h" /* Default number of unreplied requests to be considered a flood. */ #define DNP3_DEFAULT_REQ_FLOOD_COUNT 500 #define DNP3_DEFAULT_PORT "20000" /* Expected values for the start bytes. */ #define DNP3_START_BYTE0 0x05 #define DNP3_START_BYTE1 0x64 /* Minimum length for a DNP3 frame. */ #define DNP3_MIN_LEN 5 /* Length of each CRC. */ #define DNP3_CRC_LEN 2 /* DNP3 block size. After the link header a CRC is inserted after * after 16 bytes of data. */ #define DNP3_BLOCK_SIZE 16 /* Maximum transport layer sequence number. */ #define DNP3_MAX_TRAN_SEQNO 64 /* Maximum application layer sequence number. */ #define DNP3_MAX_APP_SEQNO 16 /* The number of bytes in the header that are counted as part of the * header length field. */ #define DNP3_LINK_HDR_LEN 5 /* Link function codes. */ enum { DNP3_LINK_FC_CONFIRMED_USER_DATA = 3, DNP3_LINK_FC_UNCONFIRMED_USER_DATA }; /* Reserved addresses. */ #define DNP3_RESERVED_ADDR_MIN 0xfff0 #define DNP3_RESERVED_ADDR_MAX 0xfffb /* Source addresses must be < 0xfff0. */ #define DNP3_SRC_ADDR_MAX 0xfff0 #define DNP3_OBJ_TIME_SIZE 6 /* AKA UINT48. */ #define DNP3_OBJ_G12_V1_SIZE 11 #define DNP3_OBJ_G12_V2_SIZE 11 #define DNP3_OBJ_G12_V3_SIZE 1 /* Extract the prefix code from the object qualifier. */ #define DNP3_OBJ_PREFIX(x) ((x >> 4) & 0x7) /* Extract the range code from the object qualifier. */ #define DNP3_OBJ_RANGE(x) (x & 0xf) /* Decoder event map. */ SCEnumCharMap dnp3_decoder_event_table[] = { {"FLOODED", DNP3_DECODER_EVENT_FLOODED}, {"LEN_TOO_SMALL", DNP3_DECODER_EVENT_LEN_TOO_SMALL}, {"BAD_LINK_CRC", DNP3_DECODER_EVENT_BAD_LINK_CRC}, {"BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC}, {"MALFORMED", DNP3_DECODER_EVENT_MALFORMED}, {"UNKNOWN_OBJECT", DNP3_DECODER_EVENT_UNKNOWN_OBJECT}, {NULL, -1}, }; /* Calculate the next transport sequence number. */ #define NEXT_TH_SEQNO(current) ((current + 1) % DNP3_MAX_TRAN_SEQNO) /* Calculate the next application sequence number. */ #define NEXT_APP_SEQNO(current) ((current + 1) % DNP3_MAX_APP_SEQNO) /* CRC table generated by pycrc - http://github.com/tpircher/pycrc. * - Polynomial: 0x3d65. */ static const uint16_t crc_table[256] = { 0x0000, 0x365e, 0x6cbc, 0x5ae2, 0xd978, 0xef26, 0xb5c4, 0x839a, 0xff89, 0xc9d7, 0x9335, 0xa56b, 0x26f1, 0x10af, 0x4a4d, 0x7c13, 0xb26b, 0x8435, 0xded7, 0xe889, 0x6b13, 0x5d4d, 0x07af, 0x31f1, 0x4de2, 0x7bbc, 0x215e, 0x1700, 0x949a, 0xa2c4, 0xf826, 0xce78, 0x29af, 0x1ff1, 0x4513, 0x734d, 0xf0d7, 0xc689, 0x9c6b, 0xaa35, 0xd626, 0xe078, 0xba9a, 0x8cc4, 0x0f5e, 0x3900, 0x63e2, 0x55bc, 0x9bc4, 0xad9a, 0xf778, 0xc126, 0x42bc, 0x74e2, 0x2e00, 0x185e, 0x644d, 0x5213, 0x08f1, 0x3eaf, 0xbd35, 0x8b6b, 0xd189, 0xe7d7, 0x535e, 0x6500, 0x3fe2, 0x09bc, 0x8a26, 0xbc78, 0xe69a, 0xd0c4, 0xacd7, 0x9a89, 0xc06b, 0xf635, 0x75af, 0x43f1, 0x1913, 0x2f4d, 0xe135, 0xd76b, 0x8d89, 0xbbd7, 0x384d, 0x0e13, 0x54f1, 0x62af, 0x1ebc, 0x28e2, 0x7200, 0x445e, 0xc7c4, 0xf19a, 0xab78, 0x9d26, 0x7af1, 0x4caf, 0x164d, 0x2013, 0xa389, 0x95d7, 0xcf35, 0xf96b, 0x8578, 0xb326, 0xe9c4, 0xdf9a, 0x5c00, 0x6a5e, 0x30bc, 0x06e2, 0xc89a, 0xfec4, 0xa426, 0x9278, 0x11e2, 0x27bc, 0x7d5e, 0x4b00, 0x3713, 0x014d, 0x5baf, 0x6df1, 0xee6b, 0xd835, 0x82d7, 0xb489, 0xa6bc, 0x90e2, 0xca00, 0xfc5e, 0x7fc4, 0x499a, 0x1378, 0x2526, 0x5935, 0x6f6b, 0x3589, 0x03d7, 0x804d, 0xb613, 0xecf1, 0xdaaf, 0x14d7, 0x2289, 0x786b, 0x4e35, 0xcdaf, 0xfbf1, 0xa113, 0x974d, 0xeb5e, 0xdd00, 0x87e2, 0xb1bc, 0x3226, 0x0478, 0x5e9a, 0x68c4, 0x8f13, 0xb94d, 0xe3af, 0xd5f1, 0x566b, 0x6035, 0x3ad7, 0x0c89, 0x709a, 0x46c4, 0x1c26, 0x2a78, 0xa9e2, 0x9fbc, 0xc55e, 0xf300, 0x3d78, 0x0b26, 0x51c4, 0x679a, 0xe400, 0xd25e, 0x88bc, 0xbee2, 0xc2f1, 0xf4af, 0xae4d, 0x9813, 0x1b89, 0x2dd7, 0x7735, 0x416b, 0xf5e2, 0xc3bc, 0x995e, 0xaf00, 0x2c9a, 0x1ac4, 0x4026, 0x7678, 0x0a6b, 0x3c35, 0x66d7, 0x5089, 0xd313, 0xe54d, 0xbfaf, 0x89f1, 0x4789, 0x71d7, 0x2b35, 0x1d6b, 0x9ef1, 0xa8af, 0xf24d, 0xc413, 0xb800, 0x8e5e, 0xd4bc, 0xe2e2, 0x6178, 0x5726, 0x0dc4, 0x3b9a, 0xdc4d, 0xea13, 0xb0f1, 0x86af, 0x0535, 0x336b, 0x6989, 0x5fd7, 0x23c4, 0x159a, 0x4f78, 0x7926, 0xfabc, 0xcce2, 0x9600, 0xa05e, 0x6e26, 0x5878, 0x029a, 0x34c4, 0xb75e, 0x8100, 0xdbe2, 0xedbc, 0x91af, 0xa7f1, 0xfd13, 0xcb4d, 0x48d7, 0x7e89, 0x246b, 0x1235 }; /** * \brief Compute the CRC for a buffer. * * \param buf Buffer to create CRC from. * \param len Length of buffer (number of bytes to use for CRC). */ static uint16_t DNP3ComputeCRC(const uint8_t *buf, uint32_t len) { const uint8_t *byte = buf; uint16_t crc = 0; int idx; while (len--) { idx = (crc ^ *byte) & 0xff; crc = (crc_table[idx] ^ (crc >> 8)) & 0xffff; byte++; } return ~crc & 0xffff; } /** * \brief Check the CRC of a block. * * \param block The block of data with CRC to be checked. * \param len The size of the data block. * * \retval 1 if CRC is OK, otherwise 0. */ static int DNP3CheckCRC(const uint8_t *block, uint32_t len) { #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return 1; #endif uint32_t crc_offset; uint16_t crc; /* Need at least one byte plus the CRC. */ if (len < DNP3_CRC_LEN + 1) { return 0; } crc_offset = len - DNP3_CRC_LEN; crc = DNP3ComputeCRC(block, len - DNP3_CRC_LEN); if (((crc & 0xff) == block[crc_offset]) && ((crc >> 8) == block[crc_offset + 1])) { return 1; } return 0; } /** * \brief Check the CRC of the link header. * * \param header Point to the link header. * * \retval 1 if header CRC is OK, otherwise 0. */ static int DNP3CheckLinkHeaderCRC(const DNP3LinkHeader *header) { return DNP3CheckCRC((uint8_t *)header, sizeof(DNP3LinkHeader)); } /** * \brief Check user data CRCs. * * \param data Pointer to user data. * \param len Length of user data. * * \retval 1 if CRCs are OK, otherwise 0. */ static int DNP3CheckUserDataCRCs(const uint8_t *data, uint32_t len) { uint32_t offset = 0; uint32_t block_size; while (offset < len) { if (len - offset >= DNP3_BLOCK_SIZE + DNP3_CRC_LEN) { block_size = DNP3_BLOCK_SIZE + DNP3_CRC_LEN; } else { block_size = len - offset; } if (!DNP3CheckCRC(data + offset, block_size)) { /* Once failed, may as well return immediately. */ return 0; } offset += block_size; } return 1; } /** * \brief Check the DNP3 frame start bytes. * * \retval 1 if valid, 0 if not. */ static int DNP3CheckStartBytes(const DNP3LinkHeader *header) { return header->start_byte0 == DNP3_START_BYTE0 && header->start_byte1 == DNP3_START_BYTE1; } /* Some DNP3 servers start with a banner. */ #define DNP3_BANNER "DNP3" /** * \brief Check if a frame contains a banner. * * Some servers (outstations) appear to send back a banner that fails * the normal frame checks. So first check for a banner. * * \retval 1 if a banner is found, 0 if not. */ static int DNP3ContainsBanner(const uint8_t *input, uint32_t len) { return BasicSearch(input, len, (uint8_t *)DNP3_BANNER, strlen(DNP3_BANNER)) != NULL; } /** * \brief DNP3 probing parser. */ static uint16_t DNP3ProbingParser(Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir) { const DNP3LinkHeader *const hdr = (const DNP3LinkHeader *)input; const bool toserver = (direction & STREAM_TOSERVER) != 0; /* May be a banner. */ if (DNP3ContainsBanner(input, len)) { SCLogDebug("Packet contains a DNP3 banner."); bool is_banner = true; // magic 0x100 = 256 seems good enough for (uint32_t i = 0; i < len && i < 0x100; i++) { if (!isprint(input[i])) { is_banner = false; break; } } if (is_banner) { if (toserver) { *rdir = STREAM_TOCLIENT; } return ALPROTO_DNP3; } } /* Check that we have the minimum amount of bytes. */ if (len < sizeof(DNP3LinkHeader)) { SCLogDebug("Length too small to be a DNP3 header."); return ALPROTO_UNKNOWN; } /* Verify start value (from AN2013-004b). */ if (!DNP3CheckStartBytes(hdr)) { SCLogDebug("Invalid start bytes."); return ALPROTO_FAILED; } /* Verify minimum length. */ if (hdr->len < DNP3_MIN_LEN) { SCLogDebug("Packet too small to be a valid DNP3 fragment."); return ALPROTO_FAILED; } // Test compatibility between direction and dnp3.ctl.direction if ((DNP3_LINK_DIR(hdr->control) != 0) != toserver) { *rdir = toserver ? STREAM_TOCLIENT : STREAM_TOSERVER; } SCLogDebug("Detected DNP3."); return ALPROTO_DNP3; } /** * \brief Calculate the length of the transport layer with CRCs removed. * * \param input_len The length of the transport layer buffer. * * \retval The length of the buffer after CRCs are removed. */ static int DNP3CalculateTransportLengthWithoutCRCs(uint32_t input_len) { /* Too small. */ if (input_len < DNP3_CRC_LEN) { return -1; } /* Get the number of complete blocks. */ int blocks = input_len / (DNP3_BLOCK_SIZE + DNP3_CRC_LEN); /* And the number of bytes in the last block. */ int rem = input_len - (blocks * (DNP3_BLOCK_SIZE + DNP3_CRC_LEN)); if (rem) { if (rem < DNP3_CRC_LEN) { return -1; } return (blocks * DNP3_BLOCK_SIZE) + (rem - DNP3_CRC_LEN); } else { return (blocks * DNP3_BLOCK_SIZE); } } /** * \brief Reassemble the application layer by stripping the CRCs. * * Remove the CRCs from the user data blocks. The output is the user * data with the CRCs removed as well as the transport header removed, * but the input data still needs to include the transport header as * its part of the first user data block. * * If the output length passed in is non-null, the new input data will * be appended, and the output length pointer incremented as needed. * * \param input Input buffer starting at the transport header (which * will be removed from the output). * \param input_len Length of the input buffer. * \param output Pointer to output buffer (may be realloc'd). * \param output_len Pointer to output length. * * \retval 1 if reassembly was successful, otherwise 0. */ static int DNP3ReassembleApplicationLayer(const uint8_t *input, uint32_t input_len, uint8_t **output, uint32_t *output_len) { int len = DNP3CalculateTransportLengthWithoutCRCs(input_len); if (len <= 0) { return 0; } /* Remove one byte for the transport header and make sure we have * at least one byte of user data. */ if (--len < 1) { return 0; } if (*output == NULL) { *output = SCCalloc(1, len); if (unlikely(*output == NULL)) { return 0; } } else { uint8_t *ptr = SCRealloc(*output, (size_t)(*output_len + len)); if (unlikely(ptr == NULL)) { return 0; } *output = ptr; } int offset = 0, block_size; while ((uint32_t)offset < input_len) { if (input_len - offset > DNP3_BLOCK_SIZE + DNP3_CRC_LEN) { block_size = DNP3_BLOCK_SIZE + DNP3_CRC_LEN; } else { block_size = input_len - offset; } /* If handling the first block (offset is 0), trim off the * first byte which is the transport header, and not part of * the application data. */ if (offset == 0) { offset++; block_size--; } /* Need at least 3 bytes to continue. One for application * data, and 2 for the CRC. If not, return failure for * malformed frame. */ if (block_size < DNP3_CRC_LEN + 1) { SCLogDebug("Not enough data to continue."); return 0; } /* Make sure there is enough space to write into. */ if (block_size - DNP3_CRC_LEN > len) { SCLogDebug("Not enough data to continue."); return 0; } memcpy(*output + *output_len, input + offset, block_size - DNP3_CRC_LEN); *output_len += block_size - DNP3_CRC_LEN; offset += block_size; len -= block_size - DNP3_CRC_LEN; } return 1; } /** * \brief Allocate a DNP3 state object. * * The DNP3 state object represents a single DNP3 TCP session. */ static void *DNP3StateAlloc(void *orig_state, AppProto proto_orig) { SCEnter(); DNP3State *dnp3; dnp3 = (DNP3State *)SCCalloc(1, sizeof(DNP3State)); if (unlikely(dnp3 == NULL)) { return NULL; } TAILQ_INIT(&dnp3->tx_list); SCReturnPtr(dnp3, "void"); } /** * \brief Set a DNP3 application layer event. * * Sets an event on the current transaction object. */ static void DNP3SetEvent(DNP3State *dnp3, uint8_t event) { if (dnp3 && dnp3->curr) { AppLayerDecoderEventsSetEventRaw(&dnp3->curr->tx_data.events, event); dnp3->events++; } else { SCLogWarning("Failed to set event, state or tx pointer was NULL."); } } /** * \brief Set a DNP3 application layer event on a transaction. */ static void DNP3SetEventTx(DNP3Transaction *tx, uint8_t event) { AppLayerDecoderEventsSetEventRaw(&tx->tx_data.events, event); tx->dnp3->events++; } /** * \brief Allocation a DNP3 transaction. */ static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3, bool request) { DNP3Transaction *tx = SCCalloc(1, sizeof(DNP3Transaction)); if (unlikely(tx == NULL)) { return NULL; } dnp3->transaction_max++; dnp3->unreplied++; dnp3->curr = tx; tx->dnp3 = dnp3; tx->tx_num = dnp3->transaction_max; tx->is_request = request; if (tx->is_request) { tx->tx_data.detect_flags_tc |= APP_LAYER_TX_SKIP_INSPECT_FLAG; } else { tx->tx_data.detect_flags_ts |= APP_LAYER_TX_SKIP_INSPECT_FLAG; } TAILQ_INIT(&tx->objects); TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next); /* Check for flood state. */ if (dnp3->unreplied > DNP3_DEFAULT_REQ_FLOOD_COUNT) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_FLOODED); dnp3->flooded = 1; } return tx; } /** * \brief Calculate the length of a link frame with CRCs. * * This is required as the length parameter in the DNP3 header does not * include the added CRCs. * * \param length The length from the DNP3 link header. * * \retval The length of the frame with CRCs included or 0 if the length isn't * long enough to be a valid DNP3 frame. */ static uint32_t DNP3CalculateLinkLength(uint8_t length) { uint32_t frame_len = 0; int rem; /* Fail early if the length is less than the minimum size. */ if (length < DNP3_LINK_HDR_LEN) { return 0; } /* Subtract the 5 bytes of the header that are included in the * length. */ length -= DNP3_LINK_HDR_LEN; rem = length % DNP3_BLOCK_SIZE; frame_len = (length / DNP3_BLOCK_SIZE) * (DNP3_BLOCK_SIZE + DNP3_CRC_LEN); if (rem) { frame_len += rem + DNP3_CRC_LEN; } return frame_len + sizeof(DNP3LinkHeader); } /** * \brief Check if the link function code specifies user data. * * \param header Point to link header. * * \retval 1 if frame contains user data, otherwise 0. */ static int DNP3IsUserData(const DNP3LinkHeader *header) { switch (DNP3_LINK_FC(header->control)) { case DNP3_LINK_FC_CONFIRMED_USER_DATA: case DNP3_LINK_FC_UNCONFIRMED_USER_DATA: return 1; default: return 0; } } /** * \brief Check if the frame has user data. * * Check if the DNP3 frame actually has user data by checking if data * exists after the headers. * * \retval 1 if user data exists, otherwise 0. */ static int DNP3HasUserData(const DNP3LinkHeader *header, uint8_t direction) { if (direction == STREAM_TOSERVER) { return header->len >= DNP3_LINK_HDR_LEN + sizeof(DNP3TransportHeader) + sizeof(DNP3ApplicationHeader); } else { return header->len >= DNP3_LINK_HDR_LEN + sizeof(DNP3TransportHeader) + sizeof(DNP3ApplicationHeader) + sizeof(DNP3InternalInd); } } /** * \brief Reset a DNP3Buffer. */ static void DNP3BufferReset(DNP3Buffer *buffer) { buffer->offset = 0; buffer->len = 0; } /** * \brief Add data to a DNP3 buffer, enlarging the buffer if required. * * \param buffer Buffer to add data data. * \param data Data to be added to buffer. * \param len Size of data to be added to buffer. * * \param 1 if data was added successful, otherwise 0. */ static int DNP3BufferAdd(DNP3Buffer *buffer, const uint8_t *data, uint32_t len) { if (buffer->size == 0) { buffer->buffer = SCCalloc(1, len); if (unlikely(buffer->buffer == NULL)) { return 0; } buffer->size = len; } else if (buffer->len + len > buffer->size) { uint8_t *tmp = SCRealloc(buffer->buffer, buffer->len + len); if (unlikely(tmp == NULL)) { return 0; } buffer->buffer = tmp; buffer->size = buffer->len + len; } memcpy(buffer->buffer + buffer->len, data, len); buffer->len += len; return 1; } /** * \brief Trim a DNP3 buffer. * * Trimming a buffer moves the data in the buffer up to the front of * the buffer freeing up room at the end for more incoming data. * * \param buffer The buffer to trim. */ static void DNP3BufferTrim(DNP3Buffer *buffer) { if (buffer->offset == buffer->len) { DNP3BufferReset(buffer); } else if (buffer->offset > 0) { memmove(buffer->buffer, buffer->buffer + buffer->offset, buffer->len - buffer->offset); buffer->len = buffer->len - buffer->offset; buffer->offset = 0; } } /** * \brief Free a DNP3 object. */ static void DNP3ObjectFree(DNP3Object *object) { if (object->points != NULL) { DNP3FreeObjectPointList(object->group, object->variation, object->points); } SCFree(object); } /** * \brief Allocate a DNP3 object. */ static DNP3Object *DNP3ObjectAlloc(void) { DNP3Object *object = SCCalloc(1, sizeof(*object)); if (unlikely(object == NULL)) { return NULL; } object->points = DNP3PointListAlloc(); if (object->points == NULL) { DNP3ObjectFree(object); return NULL; } return object; } /** * \brief Decode DNP3 application objects. * * This function decoded known DNP3 application objects. As the * protocol isn't self describing, we can only decode the buffer while * the application objects are known. As soon as an unknown * group/variation is hit, we must stop processing. * * \param buf the input buffer * \param len length of the input buffer * \param objects pointer to list where decoded objects will be stored. * * \retval 1 if all objects decoded, 0 if all objects could not be decoded ( * unknown group/variations) */ static int DNP3DecodeApplicationObjects(DNP3Transaction *tx, const uint8_t *buf, uint32_t len, DNP3ObjectList *objects) { int retval = 0; if (buf == NULL || len == 0) { return 1; } while (len) { uint32_t offset = 0; if (len < sizeof(DNP3ObjHeader)) { goto done; } DNP3ObjHeader *header = (DNP3ObjHeader *)buf; offset += sizeof(DNP3ObjHeader); DNP3Object *object = DNP3ObjectAlloc(); if (unlikely(object == NULL)) { goto done; } TAILQ_INSERT_TAIL(objects, object, next); object->group = header->group; object->variation = header->variation; object->qualifier = header->qualifier; object->prefix_code = DNP3_OBJ_PREFIX(header->qualifier); object->range_code = DNP3_OBJ_RANGE(header->qualifier); /* IEEE 1815-2012, Table 4-5. */ switch (object->range_code) { case 0x00: case 0x03: { /* 1 octet start and stop indexes OR 1 octet start and * stop virtual addresses. */ if (offset + (sizeof(uint8_t) * 2) > len) { /* Not enough data. */ SCLogDebug("Not enough data."); goto not_enough_data; } object->start = buf[offset++]; object->stop = buf[offset++]; object->count = object->stop - object->start + 1; break; } case 0x01: case 0x04: { /* 2 octet start and stop indexes OR 2 octect start * and stop virtual addresses. */ if (offset + (sizeof(uint16_t) * 2) > len) { /* Not enough data. */ SCLogDebug("Not enough data."); goto not_enough_data; } object->start = DNP3_SWAP16(*(uint16_t *)(buf + offset)); offset += sizeof(uint16_t); object->stop = DNP3_SWAP16(*(uint16_t *)(buf + offset)); offset += sizeof(uint16_t); object->count = object->stop - object->start + 1; break; } case 0x02: case 0x05: { /* 4 octet start and stop indexes OR 4 octect start * and stop virtual addresses. */ if (offset + (sizeof(uint32_t) * 2) > len) { /* Not enough data. */ SCLogDebug("Not enough data."); goto not_enough_data; } object->start = DNP3_SWAP32(*(uint32_t *)(buf + offset)); offset += sizeof(uint32_t); object->stop = DNP3_SWAP32(*(uint32_t *)(buf + offset)); offset += sizeof(uint32_t); object->count = object->stop - object->start + 1; break; } case 0x06: /* No range field. */ object->count = 0; break; case 0x07: /* 1 octet count of objects. */ if (offset + sizeof(uint8_t) > len) { SCLogDebug("Not enough data."); goto not_enough_data; } object->count = buf[offset]; offset += sizeof(uint8_t); break; case 0x08: { /* 2 octet count of objects. */ if (offset + sizeof(uint16_t) > len) { SCLogDebug("Not enough data."); goto not_enough_data; } object->count = DNP3_SWAP16(*(uint16_t *)(buf + offset)); offset += sizeof(uint16_t); break; } case 0x09: { /* 4 octet count of objects. */ if (offset + sizeof(uint32_t) > len) { SCLogDebug("Not enough data."); goto not_enough_data; } object->count = DNP3_SWAP32(*(uint32_t *)(buf + offset)); offset += sizeof(uint32_t); break; } case 0x0b: { if (offset + sizeof(uint8_t) > len) { /* Not enough data. */ SCLogDebug("Not enough data."); goto not_enough_data; } object->count = *(uint8_t *)(buf + offset); offset += sizeof(uint8_t); break; } default: SCLogDebug("Range code 0x%02x is reserved.", object->range_code); goto done; } buf += offset; len -= offset; if (object->variation == 0 || object->count == 0) { goto next; } int event = DNP3DecodeObject(header->group, header->variation, &buf, &len, object->prefix_code, object->start, object->count, object->points); if (event) { DNP3SetEventTx(tx, DNP3_DECODER_EVENT_UNKNOWN_OBJECT); goto done; } next: continue; } /* All objects were decoded. */ retval = 1; not_enough_data: done: return retval; } /** * \brief Handle DNP3 request user data. * * \param dnp3 the current DNP3State * \param input pointer to the DNP3 frame (starting with link header) * \param input_len length of the input frame */ static void DNP3HandleUserDataRequest(DNP3State *dnp3, const uint8_t *input, uint32_t input_len) { DNP3LinkHeader *lh; DNP3TransportHeader th; DNP3ApplicationHeader *ah; DNP3Transaction *tx = NULL, *ttx; lh = (DNP3LinkHeader *)input; if (!DNP3CheckUserDataCRCs(input + sizeof(DNP3LinkHeader), input_len - sizeof(DNP3LinkHeader))) { return; } th = input[sizeof(DNP3LinkHeader)]; if (!DNP3_TH_FIR(th)) { TAILQ_FOREACH(ttx, &dnp3->tx_list, next) { if (ttx->lh.src == lh->src && ttx->lh.dst == lh->dst && ttx->is_request && !ttx->done && NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->th)) == DNP3_TH_SEQ(th)) { tx = ttx; break; } } if (tx == NULL) { return; } /* Update the saved transport header so subsequent segments * will be matched to this sequence number. */ tx->th = th; } else { ah = (DNP3ApplicationHeader *)(input + sizeof(DNP3LinkHeader) + sizeof(DNP3TransportHeader)); /* Ignore confirms - for now. */ if (ah->function_code == DNP3_APP_FC_CONFIRM) { return; } /* Create a transaction. */ tx = DNP3TxAlloc(dnp3, true); if (unlikely(tx == NULL)) { return; } tx->lh = *lh; tx->th = th; tx->ah = *ah; } if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader), input_len - sizeof(DNP3LinkHeader), &tx->buffer, &tx->buffer_len)) { /* Malformed, set event and mark as done. */ DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED); tx->done = 1; return; } /* If this is not the final segment, just return. */ if (!DNP3_TH_FIN(th)) { return; } tx->done = 1; if (DNP3DecodeApplicationObjects(tx, tx->buffer + sizeof(DNP3ApplicationHeader), tx->buffer_len - sizeof(DNP3ApplicationHeader), &tx->objects)) { tx->complete = 1; } } static void DNP3HandleUserDataResponse(DNP3State *dnp3, const uint8_t *input, uint32_t input_len) { DNP3LinkHeader *lh; DNP3TransportHeader th; DNP3ApplicationHeader *ah; DNP3InternalInd *iin; DNP3Transaction *tx = NULL, *ttx; uint32_t offset = 0; lh = (DNP3LinkHeader *)input; offset += sizeof(DNP3LinkHeader); if (!DNP3CheckUserDataCRCs(input + offset, input_len - offset)) { return; } th = input[offset++]; if (!DNP3_TH_FIR(th)) { TAILQ_FOREACH(ttx, &dnp3->tx_list, next) { if (ttx->lh.src == lh->src && ttx->lh.dst == lh->dst && !ttx->is_request && !ttx->done && NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->th)) == DNP3_TH_SEQ(th)) { tx = ttx; break; } } if (tx == NULL) { return; } /* Replace the transport header in the transaction with this * one in case there are more frames. */ tx->th = th; } else { ah = (DNP3ApplicationHeader *)(input + offset); offset += sizeof(DNP3ApplicationHeader); iin = (DNP3InternalInd *)(input + offset); tx = DNP3TxAlloc(dnp3, false); if (unlikely(tx == NULL)) { return; } tx->lh = *lh; tx->th = th; tx->ah = *ah; tx->iin = *iin; } BUG_ON(tx->is_request); if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader), input_len - sizeof(DNP3LinkHeader), &tx->buffer, &tx->buffer_len)) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED); return; } if (!DNP3_TH_FIN(th)) { return; } tx->done = 1; offset = sizeof(DNP3ApplicationHeader) + sizeof(DNP3InternalInd); if (DNP3DecodeApplicationObjects( tx, tx->buffer + offset, tx->buffer_len - offset, &tx->objects)) { tx->complete = 1; } } /** * \brief Decode the DNP3 request link layer. * * \retval number of bytes processed or -1 if the data stream does not look * like DNP3. */ static int DNP3HandleRequestLinkLayer(DNP3State *dnp3, const uint8_t *input, uint32_t input_len) { SCEnter(); uint32_t processed = 0; while (input_len) { /* Need at least enough bytes for a DNP3 header. */ if (input_len < sizeof(DNP3LinkHeader)) { break; } DNP3LinkHeader *header = (DNP3LinkHeader *)input; if (!DNP3CheckStartBytes(header)) { goto error; } if (!DNP3CheckLinkHeaderCRC(header)) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_LINK_CRC); goto error; } uint32_t frame_len = DNP3CalculateLinkLength(header->len); if (frame_len == 0) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL); goto error; } if (input_len < frame_len) { /* Insufficient data, just break - will wait for more data. */ break; } /* Ignore non-user data for now. */ if (!DNP3IsUserData(header)) { goto next; } /* Make sure the header length is large enough for transport and * application headers. */ if (!DNP3HasUserData(header, STREAM_TOSERVER)) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL); goto next; } if (!DNP3CheckUserDataCRCs(input + sizeof(DNP3LinkHeader), frame_len - sizeof(DNP3LinkHeader))) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC); goto next; } DNP3HandleUserDataRequest(dnp3, input, frame_len); next: /* Advance the input buffer. */ input += frame_len; input_len -= frame_len; processed += frame_len; } SCReturnInt(processed); error: /* Error out. Should only happen if this doesn't look like a DNP3 * frame. */ SCReturnInt(-1); } /** * \brief Handle incoming request data. * * The actual request PDU parsing is done in * DNP3HandleRequestLinkLayer. This function takes care of buffering TCP * date if a segment does not contain a complete frame (or contains * multiple frames, but not the complete final frame). */ static AppLayerResult DNP3ParseRequest(Flow *f, void *state, AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data) { SCEnter(); DNP3State *dnp3 = (DNP3State *)state; DNP3Buffer *buffer = &dnp3->request_buffer; int processed = 0; const uint8_t *input = StreamSliceGetData(&stream_slice); uint32_t input_len = StreamSliceGetDataLen(&stream_slice); if (input_len == 0) { SCReturnStruct(APP_LAYER_OK); } if (buffer->len) { if (!DNP3BufferAdd(buffer, input, input_len)) { goto error; } processed = DNP3HandleRequestLinkLayer(dnp3, buffer->buffer + buffer->offset, buffer->len - buffer->offset); if (processed < 0) { goto error; } buffer->offset += processed; DNP3BufferTrim(buffer); } else { processed = DNP3HandleRequestLinkLayer(dnp3, input, input_len); if (processed < 0) { SCLogDebug("Failed to process request link layer."); goto error; } input += processed; input_len -= processed; /* Not all data was processed, buffer it. */ if (input_len) { if (!DNP3BufferAdd(buffer, input, input_len)) { goto error; } } } SCReturnStruct(APP_LAYER_OK); error: /* Reset the buffer. */ DNP3BufferReset(buffer); SCReturnStruct(APP_LAYER_ERROR); } /** * \brief Decode the DNP3 response link layer. * * \retval number of bytes processed or -1 if the data stream does not * like look DNP3. */ static int DNP3HandleResponseLinkLayer(DNP3State *dnp3, const uint8_t *input, uint32_t input_len) { SCEnter(); uint32_t processed = 0; while (input_len) { /* Need at least enough bytes for a DNP3 header. */ if (input_len < sizeof(DNP3LinkHeader)) { break; } DNP3LinkHeader *header = (DNP3LinkHeader *)input; if (!DNP3CheckStartBytes(header)) { goto error; } if (!DNP3CheckLinkHeaderCRC(header)) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_LINK_CRC); goto error; } /* Calculate the number of bytes needed to for this frame. */ uint32_t frame_len = DNP3CalculateLinkLength(header->len); if (frame_len == 0) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL); goto error; } if (input_len < frame_len) { /* Insufficient data, just break - will wait for more data. */ break; } /* Only handle user data frames for now. */ if (!DNP3IsUserData(header)) { goto next; } /* Make sure the header length is large enough for transport and * application headers. */ if (!DNP3HasUserData(header, STREAM_TOCLIENT)) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL); goto error; } if (!DNP3CheckUserDataCRCs(input + sizeof(DNP3LinkHeader), frame_len - sizeof(DNP3LinkHeader))) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC); goto next; } DNP3HandleUserDataResponse(dnp3, input, frame_len); next: /* Advance the input buffer. */ input += frame_len; input_len -= frame_len; processed += frame_len; } SCReturnInt(processed); error: /* Error out. Should only happen if the data stream no longer * looks like DNP3. */ SCReturnInt(-1); } /** * \brief Parse incoming data. * * This is the entry function for DNP3 application layer data. Its * main responsibility is buffering incoming data that cannot be * processed. * * See DNP3ParseResponsePDUs for DNP3 frame handling. */ static AppLayerResult DNP3ParseResponse(Flow *f, void *state, AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data) { SCEnter(); DNP3State *dnp3 = (DNP3State *)state; DNP3Buffer *buffer = &dnp3->response_buffer; int processed; const uint8_t *input = StreamSliceGetData(&stream_slice); uint32_t input_len = StreamSliceGetDataLen(&stream_slice); if (buffer->len) { if (!DNP3BufferAdd(buffer, input, input_len)) { goto error; } processed = DNP3HandleResponseLinkLayer(dnp3, buffer->buffer + buffer->offset, buffer->len - buffer->offset); if (processed < 0) { goto error; } buffer->offset += processed; DNP3BufferTrim(buffer); } else { /* Check if this is a banner, ignore if it is. */ if (DNP3ContainsBanner(input, input_len)) { goto done; } processed = DNP3HandleResponseLinkLayer(dnp3, input, input_len); if (processed < 0) { goto error; } input += processed; input_len -= processed; /* Not all data was processed, buffer it. */ if (input_len) { if (!DNP3BufferAdd(buffer, input, input_len)) { goto error; } } } done: SCReturnStruct(APP_LAYER_OK); error: /* An error occurred while processing DNP3 frames. Dump the * buffer as we can't be assured that they are valid anymore. */ DNP3BufferReset(buffer); SCReturnStruct(APP_LAYER_ERROR); } static void *DNP3GetTx(void *alstate, uint64_t tx_id) { SCEnter(); DNP3State *dnp3 = (DNP3State *)alstate; DNP3Transaction *tx = NULL; uint64_t tx_num = tx_id + 1; if (dnp3->curr && dnp3->curr->tx_num == (tx_num)) { SCReturnPtr(dnp3->curr, "void"); } TAILQ_FOREACH(tx, &dnp3->tx_list, next) { if (tx_num != tx->tx_num) { continue; } SCReturnPtr(tx, "void"); } SCReturnPtr(NULL, "void"); } static uint64_t DNP3GetTxCnt(void *state) { SCEnter(); uint64_t count = ((uint64_t)((DNP3State *)state)->transaction_max); SCReturnUInt(count); } /** * \brief Free all the objects in a DNP3ObjectList. */ static void DNP3TxFreeObjectList(DNP3ObjectList *objects) { DNP3Object *object; while ((object = TAILQ_FIRST(objects)) != NULL) { TAILQ_REMOVE(objects, object, next); DNP3ObjectFree(object); } } /** * \brief Free a DNP3 transaction. */ static void DNP3TxFree(DNP3Transaction *tx) { SCEnter(); if (tx->buffer != NULL) { SCFree(tx->buffer); } AppLayerDecoderEventsFreeEvents(&tx->tx_data.events); if (tx->tx_data.de_state != NULL) { DetectEngineStateFree(tx->tx_data.de_state); } DNP3TxFreeObjectList(&tx->objects); SCFree(tx); SCReturn; } /** * \brief Free a transaction by ID on a specific DNP3 state. * * This function is called by the app-layer to free a transaction on a * specific DNP3 state object. */ static void DNP3StateTxFree(void *state, uint64_t tx_id) { SCEnter(); DNP3State *dnp3 = state; DNP3Transaction *tx = NULL, *ttx; uint64_t tx_num = tx_id + 1; TAILQ_FOREACH_SAFE(tx, &dnp3->tx_list, next, ttx) { if (tx->tx_num != tx_num) { continue; } if (tx == dnp3->curr) { dnp3->curr = NULL; } if (tx->tx_data.events != NULL) { if (tx->tx_data.events->cnt <= dnp3->events) { dnp3->events -= tx->tx_data.events->cnt; } else { dnp3->events = 0; } } dnp3->unreplied--; /* Check flood state. */ if (dnp3->flooded && dnp3->unreplied < DNP3_DEFAULT_REQ_FLOOD_COUNT) { dnp3->flooded = 0; } TAILQ_REMOVE(&dnp3->tx_list, tx, next); DNP3TxFree(tx); break; } SCReturn; } /** * \brief Free a DNP3 state. */ static void DNP3StateFree(void *state) { SCEnter(); DNP3State *dnp3 = state; DNP3Transaction *tx; if (state != NULL) { while ((tx = TAILQ_FIRST(&dnp3->tx_list)) != NULL) { TAILQ_REMOVE(&dnp3->tx_list, tx, next); DNP3TxFree(tx); } if (dnp3->request_buffer.buffer != NULL) { SCFree(dnp3->request_buffer.buffer); } if (dnp3->response_buffer.buffer != NULL) { SCFree(dnp3->response_buffer.buffer); } SCFree(dnp3); } SCReturn; } /** * \brief Called by the app-layer to get the state progress. */ static int DNP3GetAlstateProgress(void *tx, uint8_t direction) { DNP3Transaction *dnp3tx = (DNP3Transaction *)tx; DNP3State *dnp3 = dnp3tx->dnp3; int retval = 0; /* If flooded, "ack" old transactions. */ if (dnp3->flooded && (dnp3->transaction_max - dnp3tx->tx_num >= DNP3_DEFAULT_REQ_FLOOD_COUNT)) { SCLogDebug("flooded: returning tx as done."); SCReturnInt(1); } if (dnp3tx->complete) retval = 1; SCReturnInt(retval); } /** * \brief App-layer support. */ static int DNP3StateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type) { *event_id = SCMapEnumNameToValue(event_name, dnp3_decoder_event_table); if (*event_id == -1) { SCLogError("Event \"%s\" not present in " "the DNP3 enum event map table.", event_name); return -1; } *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; return 0; } /** * \brief App-layer support. */ static int DNP3StateGetEventInfoById(int event_id, const char **event_name, AppLayerEventType *event_type) { *event_name = SCMapEnumValueToName(event_id, dnp3_decoder_event_table); if (*event_name == NULL) { SCLogError("Event \"%d\" not present in " "the DNP3 enum event map table.", event_id); return -1; } *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; return 0; } static AppLayerTxData *DNP3GetTxData(void *vtx) { DNP3Transaction *tx = (DNP3Transaction *)vtx; return &tx->tx_data; } static AppLayerStateData *DNP3GetStateData(void *vstate) { DNP3State *state = (DNP3State *)vstate; return &state->state_data; } /** * \brief Check if the prefix code is a size prefix. * * \retval 1 if the prefix_code specifies a size prefix, 0 if not. */ int DNP3PrefixIsSize(uint8_t prefix_code) { switch (prefix_code) { case 0x04: case 0x05: case 0x06: return 1; break; default: return 0; } } static AppLayerGetTxIterTuple DNP3GetTxIterator(const uint8_t ipproto, const AppProto alproto, void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state) { DNP3State *dnp_state = (DNP3State *)alstate; AppLayerGetTxIterTuple no_tuple = { NULL, 0, false }; if (dnp_state) { DNP3Transaction *tx_ptr; if (state->un.ptr == NULL) { tx_ptr = TAILQ_FIRST(&dnp_state->tx_list); } else { tx_ptr = (DNP3Transaction *)state->un.ptr; } if (tx_ptr) { while (tx_ptr->tx_num < min_tx_id + 1) { tx_ptr = TAILQ_NEXT(tx_ptr, next); if (!tx_ptr) { return no_tuple; } } if (tx_ptr->tx_num >= max_tx_id + 1) { return no_tuple; } state->un.ptr = TAILQ_NEXT(tx_ptr, next); AppLayerGetTxIterTuple tuple = { .tx_ptr = tx_ptr, .tx_id = tx_ptr->tx_num - 1, .has_next = (state->un.ptr != NULL), }; return tuple; } } return no_tuple; } /** * \brief Register the DNP3 application protocol parser. */ void RegisterDNP3Parsers(void) { SCEnter(); const char *proto_name = "dnp3"; if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("tcp", proto_name, false)) { AppLayerProtoDetectRegisterProtocol(ALPROTO_DNP3, proto_name); if (RunmodeIsUnittests()) { AppLayerProtoDetectPPRegister(IPPROTO_TCP, DNP3_DEFAULT_PORT, ALPROTO_DNP3, 0, sizeof(DNP3LinkHeader), STREAM_TOSERVER, DNP3ProbingParser, DNP3ProbingParser); } else { if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, proto_name, ALPROTO_DNP3, 0, sizeof(DNP3LinkHeader), DNP3ProbingParser, DNP3ProbingParser)) { return; } } } else { SCLogConfig("Protocol detection and parser disabled for DNP3."); SCReturn; } if (AppLayerParserConfParserEnabled("tcp", proto_name)) { SCLogConfig("Registering DNP3/tcp parsers."); AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_DNP3, STREAM_TOSERVER, DNP3ParseRequest); AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_DNP3, STREAM_TOCLIENT, DNP3ParseResponse); AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_DNP3, DNP3StateAlloc, DNP3StateFree); AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTx); AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTxIterator); AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTxCnt); AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3StateTxFree); AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetAlstateProgress); AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_DNP3, 1, 1); AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_DNP3, DNP3StateGetEventInfo); AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_DNP3, DNP3StateGetEventInfoById); AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTxData); AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetStateData); } else { SCLogConfig("Parser disabled for protocol %s. " "Protocol detection still on.", proto_name); } #ifdef UNITTESTS AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_DNP3, DNP3ParserRegisterTests); #endif SCReturn; } #ifdef UNITTESTS #include "flow-util.h" #include "stream-tcp.h" /** * \brief Utility function to fix CRCs when mangling a frame. */ static void DNP3FixCrc(uint8_t *data, uint32_t len) { uint32_t block_size; while (len) { if (len >= DNP3_BLOCK_SIZE + DNP3_CRC_LEN) { block_size = DNP3_BLOCK_SIZE; } else { block_size = len - DNP3_CRC_LEN; } uint16_t crc = DNP3ComputeCRC(data, block_size); data[block_size + 1] = (crc >> 8) & 0xff; data[block_size] = crc & 0xff; data += block_size + DNP3_CRC_LEN; len -= block_size + DNP3_CRC_LEN; } } /** * \test Test CRC checking on partial and full blocks. */ static int DNP3ParserTestCheckCRC(void) { uint8_t request[] = { /* DNP3 start. */ 0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00, 0xa5, 0xe9, /* Transport header. */ 0xff, /* Application layer - segment 1. */ 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* Application layer - segment 2. */ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; /* Check link header CRC. */ FAIL_IF(!DNP3CheckCRC(request, sizeof(DNP3LinkHeader))); /* Check first application layer segment. */ FAIL_IF(!DNP3CheckCRC(request + sizeof(DNP3LinkHeader), DNP3_BLOCK_SIZE + DNP3_CRC_LEN)); #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* Change a byte in link header, should fail now. */ request[2]++; FAIL_IF(DNP3CheckCRC(request, sizeof(DNP3LinkHeader))); /* Change a byte in the first application segment, should fail * now. */ request[sizeof(DNP3LinkHeader) + 3]++; FAIL_IF(DNP3CheckCRC(request + sizeof(DNP3LinkHeader), DNP3_BLOCK_SIZE + DNP3_CRC_LEN)); #endif PASS; } /** * \test Test validation of all CRCs in user data. */ static int DNP3CheckUserDataCRCsTest(void) { /* Multi-block data with valid CRCs. */ uint8_t data_valid[] = { 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* CRC. */ }; FAIL_IF(!DNP3CheckUserDataCRCs(data_valid, sizeof(data_valid))); #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* Multi-block data with one non-crc byte altered. */ uint8_t data_invalid[] = { 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0x00, 0x00, 0x00, 0x00, 0x01, /* Invalid byte. */ 0xff, 0xff, /* CRC. */ }; FAIL_IF(DNP3CheckUserDataCRCs(data_invalid, sizeof(data_invalid))); /* 1 byte - need at least 3. */ uint8_t one_byte_nocrc[] = { 0x01 }; FAIL_IF(DNP3CheckUserDataCRCs(one_byte_nocrc, sizeof(one_byte_nocrc))); /* 2 bytes - need at least 3. */ uint8_t two_byte_nocrc[] = { 0x01, 0x02 }; FAIL_IF(DNP3CheckUserDataCRCs(two_byte_nocrc, sizeof(two_byte_nocrc))); #endif /* 3 bytes, valid CRC. */ uint8_t three_bytes_good_crc[] = { 0x00, 0x00, 0x00 }; *(uint16_t *)(three_bytes_good_crc + 1) = DNP3ComputeCRC( three_bytes_good_crc, 1); FAIL_IF(!DNP3CheckUserDataCRCs(three_bytes_good_crc, sizeof(three_bytes_good_crc))); PASS; } /** * \test Test the link layer length calculation. * * Test the calculation that converts the link provided in the DNP3 * header to the actual length of the frame. That is the length with * CRCs as the length in the header does not include CRCs. */ static int DNP3CalculateLinkLengthTest(void) { /* These are invalid. */ FAIL_IF(DNP3CalculateLinkLength(0) != 0); FAIL_IF(DNP3CalculateLinkLength(1) != 0); FAIL_IF(DNP3CalculateLinkLength(2) != 0); FAIL_IF(DNP3CalculateLinkLength(3) != 0); FAIL_IF(DNP3CalculateLinkLength(4) != 0); /* This is the minimum size. */ FAIL_IF(DNP3CalculateLinkLength(5) != 10); /* 1 full user data blocks of data. */ FAIL_IF(DNP3CalculateLinkLength(21) != 28); /* 2 full user data blocks of data. */ FAIL_IF(DNP3CalculateLinkLength(37) != 46); /* 2 full user data blocks, plus one more byte. */ /* 2 full user data blocks of data. */ FAIL_IF(DNP3CalculateLinkLength(38) != 49); /* The maximum size. */ FAIL_IF(DNP3CalculateLinkLength(255) != 292); PASS; } /** * \test The conversion of length with CRCs to the length without * CRCs. */ static int DNP3CalculateTransportLengthWithoutCRCsTest(void) { FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(0) != -1); FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(1) != -1); FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(2) != 0); FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(3) != 1); FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(16) != 14); FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(17) != 15); FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(18) != 16); /* 19 bytes is not enough for a second block. */ FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(19) != -1); /* 20 bytes really isn't enough either, but is large enough to * satisfy the CRC on the second block. */ FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(20) != 16); FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(21) != 17); PASS; } /** * \test Test the validation of the link header CRC. */ static int DNP3ParserCheckLinkHeaderCRC(void) { /* DNP3 frame with valid headers and CRCs. */ uint8_t request[] = { /* DNP3 start. */ 0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00, 0xa5, 0xe9, /* Transport header. */ 0xff, /* Application layer. */ 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; DNP3LinkHeader *header = (DNP3LinkHeader *)request; FAIL_IF(!DNP3CheckLinkHeaderCRC(header)); #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* Alter a byte in the header. */ request[4] = 0; FAIL_IF(DNP3CheckLinkHeaderCRC(header)); #endif PASS; } /** * \test Test removal of CRCs from user data. */ static int DNP3ReassembleApplicationLayerTest01(void) { uint32_t reassembled_len = 0; uint8_t *output = NULL; uint8_t payload[] = { 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* CRC. */ }; uint8_t expected[] = { 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, /* CRC removed. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, /* CRC removed. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, /* CRC removed. */ 0x00, 0x00, 0x00, 0x00, 0x00 /* CRC removed. */ }; /* Valid frame. */ FAIL_IF(!DNP3ReassembleApplicationLayer(payload, sizeof(payload), &output, &reassembled_len)); FAIL_IF(output == NULL); FAIL_IF(reassembled_len != sizeof(expected)); FAIL_IF(memcmp(expected, output, reassembled_len)); SCFree(output); /* 1 byte, invalid. */ reassembled_len = 0; output = NULL; FAIL_IF(DNP3ReassembleApplicationLayer(payload, 1, &output, &reassembled_len)); FAIL_IF(output != NULL); FAIL_IF(reassembled_len != 0); /* 2 bytes, invalid. */ reassembled_len = 0; output = NULL; FAIL_IF(DNP3ReassembleApplicationLayer(payload, 2, &output, &reassembled_len)); FAIL_IF(output != NULL); FAIL_IF(reassembled_len != 0); /* 3 bytes, minimum - but that would only be the transport header * which isn't included in the output. */ reassembled_len = 0; output = NULL; FAIL_IF(DNP3ReassembleApplicationLayer(payload, 3, &output, &reassembled_len)); FAIL_IF(output != NULL); FAIL_IF(reassembled_len != 0); /* 4 bytes is the minimum to get any reassembled data. */ reassembled_len = 0; output = NULL; FAIL_IF(!DNP3ReassembleApplicationLayer(payload, 4, &output, &reassembled_len)); FAIL_IF(output == NULL); FAIL_IF(reassembled_len != 1); /* Last block too short (by 1 byte) for data + CRC. */ uint8_t short_payload1[] = { 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0x00, 0x00 }; reassembled_len = 0; FAIL_IF(DNP3ReassembleApplicationLayer(short_payload1, sizeof(short_payload1), &output, &reassembled_len)); /* Last block too short (by 2 bytes) for data + CRC. */ uint8_t short_payload2[] = { 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0xff, 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, /* CRC. */ 0x00, }; reassembled_len = 0; FAIL_IF(DNP3ReassembleApplicationLayer(short_payload2, sizeof(short_payload2), &output, &reassembled_len)); PASS; } /** * \test Test the probing parser. */ static int DNP3ProbingParserTest(void) { uint8_t pkt[] = { 0x05, 0x64, 0x05, 0xc9, 0x03, 0x00, 0x04, 0x00, 0xbd, 0x71 }; uint8_t rdir = 0; /* Valid frame. */ FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(pkt), &rdir) != ALPROTO_DNP3); /* Send too little bytes. */ FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(DNP3LinkHeader) - 1, &rdir) != ALPROTO_UNKNOWN); /* Bad start bytes. */ pkt[0] = 0x06; FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(pkt), &rdir) != ALPROTO_FAILED); /* Restore start byte. */ pkt[0] = 0x05; /* Set the length to a value less than the minimum length of 5. */ pkt[2] = 0x03; FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(pkt), &rdir) != ALPROTO_FAILED); /* Send a banner. */ char mybanner[] = "Welcome to DNP3 SCADA."; FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, (uint8_t *)mybanner, sizeof(mybanner) - 1, &rdir) != ALPROTO_DNP3); FAIL_IF(rdir != STREAM_TOCLIENT); PASS; } /** * \test Test a basic request/response. */ static int DNP3ParserTestRequestResponse(void) { DNP3State *state = NULL; uint8_t request[] = { /* DNP3 start. */ 0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00, 0xa5, 0xe9, /* Transport header. */ 0xff, /* Application layer. */ 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; uint8_t response[] = { /* DNP3 start. */ 0x05, 0x64, 0x1c, 0x44, 0x01, 0x00, 0x02, 0x00, 0xe2, 0x59, /* Transport header. */ 0xc3, /* Application layer. */ 0xc9, 0x81, 0x00, 0x00, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x7a, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); Flow flow; TcpSession ssn; memset(&flow, 0, sizeof(flow)); memset(&ssn, 0, sizeof(ssn)); flow.protoctx = (void *)&ssn; flow.proto = IPPROTO_TCP; flow.alproto = ALPROTO_DNP3; StreamTcpInitConfig(true); SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, request, sizeof(request))); SCMutexUnlock(&flow.m); state = flow.alstate; FAIL_IF(state == NULL); DNP3Transaction *tx = DNP3GetTx(state, 0); FAIL_IF(tx == NULL); FAIL_IF(tx->tx_num != 1); FAIL_IF(tx != state->curr); FAIL_IF(tx->buffer == NULL); FAIL_IF(tx->buffer_len != 20); FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE); SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOCLIENT, response, sizeof(response))); SCMutexUnlock(&flow.m); DNP3Transaction *tx0 = DNP3GetTx(state, 1); FAIL_IF(tx0 == NULL); FAIL_IF(tx0 == tx); FAIL_IF(!tx0->done); FAIL_IF(tx0->buffer == NULL); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); FLOW_DESTROY(&flow); PASS; } /** * \test Test an unsolicited response from an outstation. * * This is kind of like a request initiated from the "server". */ static int DNP3ParserTestUnsolicitedResponseConfirm(void) { DNP3State *state = NULL; /* Unsolicited response with confirm bit set. */ uint8_t response[] = { 0x05, 0x64, 0x16, 0x44, 0x01, 0x00, 0x02, 0x00, 0x89, 0xe5, 0xc4, 0xfa, 0x82, 0x00, 0x00, 0x02, 0x02, 0x17, 0x01, 0x01, 0x81, 0xa7, 0x75, 0xd8, 0x32, 0x4c, 0x81, 0x3e, 0x01, 0xa1, 0xc9 }; /* Confirm. */ uint8_t confirm[] = { 0x05, 0x64, 0x08, 0xc4, 0x02, 0x00, 0x01, 0x00, 0xd3, 0xb7, 0xc0, 0xda, 0x00, 0x6a, 0x3d }; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); Flow flow; TcpSession ssn; memset(&flow, 0, sizeof(flow)); memset(&ssn, 0, sizeof(ssn)); flow.protoctx = (void *)&ssn; flow.proto = IPPROTO_TCP; flow.alproto = ALPROTO_DNP3; StreamTcpInitConfig(true); SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOCLIENT, response, sizeof(response))); SCMutexUnlock(&flow.m); state = flow.alstate; FAIL_IF(state == NULL); DNP3Transaction *tx = DNP3GetTx(state, 0); FAIL_IF(tx == NULL); FAIL_IF(tx->tx_num != 1); FAIL_IF(tx != state->curr); FAIL_IF(!tx->done); FAIL_IF(tx->ah.function_code != DNP3_APP_FC_UNSOLICITED_RESP); SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, confirm, sizeof(confirm))); SCMutexUnlock(&flow.m); /* Confirms are ignored currently. With the move to unidirectional transactions it might be easy to support these now. */ DNP3Transaction *resptx = DNP3GetTx(state, 1); FAIL_IF(resptx); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); FLOW_DESTROY(&flow); PASS; } /** * \test Test flood state. * * Note that flood state needs to revisited with the modification to a * unidirectional protocol. */ static int DNP3ParserTestFlooded(void) { DNP3State *state = NULL; uint8_t request[] = { /* DNP3 start. */ 0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00, 0xa5, 0xe9, /* Transport header. */ 0xff, /* Application layer. */ 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); Flow flow; TcpSession ssn; memset(&flow, 0, sizeof(flow)); memset(&ssn, 0, sizeof(ssn)); flow.protoctx = (void *)&ssn; flow.proto = IPPROTO_TCP; flow.alproto = ALPROTO_DNP3; StreamTcpInitConfig(true); SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, request, sizeof(request))); SCMutexUnlock(&flow.m); state = flow.alstate; FAIL_IF(state == NULL); DNP3Transaction *tx = DNP3GetTx(state, 0); FAIL_IF(tx == NULL); FAIL_IF(tx->tx_num != 1); FAIL_IF(tx != state->curr); FAIL_IF(tx->buffer == NULL); FAIL_IF(tx->buffer_len != 20); FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE); FAIL_IF_NOT(tx->done); FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER)); for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) { SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, request, sizeof(request))); SCMutexUnlock(&flow.m); } FAIL_IF(state->flooded); FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER)); /* One more request should trip us into flooded state. */ SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, request, sizeof(request))); SCMutexUnlock(&flow.m); FAIL_IF(!state->flooded); /* Progress for the oldest tx should return 1. */ FAIL_IF(!DNP3GetAlstateProgress(tx, 0)); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); FLOW_DESTROY(&flow); PASS; } /** * \test Test parsing of partial frames. * * As DNP3 operates over TCP, it is possible that a partial DNP3 frame * is received. Test that the partial frame will be buffered until the * remainder is seen. */ static int DNP3ParserTestPartialFrame(void) { DNP3State *state = NULL; DNP3Transaction *tx; int r; uint8_t request_partial1[] = { /* DNP3 start. */ 0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00, 0xa5, 0xe9, /* Transport header. */ 0xff, /* Application layer. */ 0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00, }; uint8_t request_partial2[] = { /* Remainder of application layer. */ 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; uint8_t response_partial1[] = { /* DNP3 start. */ 0x05, 0x64, 0x1c, 0x44, 0x01, 0x00, 0x02, 0x00, 0xe2, 0x59, /* Transport header. */ 0xc3, /* Application layer. */ 0xc9, 0x81, 0x00, 0x00, 0x0c, 0x01, 0x28, 0x01, }; uint8_t response_partial2[] = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x7a, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; /* Boiler plate for app layer setup. */ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); Flow flow; TcpSession ssn; memset(&flow, 0, sizeof(flow)); memset(&ssn, 0, sizeof(ssn)); flow.protoctx = (void *)&ssn; flow.proto = IPPROTO_TCP; flow.alproto = ALPROTO_DNP3; StreamTcpInitConfig(true); /* Pass in the first partial frame. */ SCMutexLock(&flow.m); r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, request_partial1, sizeof(request_partial1)); SCMutexUnlock(&flow.m); FAIL_IF(r != 0); /* Frame should just be buffered, but not yet processed. */ state = flow.alstate; FAIL_IF(state == NULL); FAIL_IF(state->request_buffer.len != sizeof(request_partial1)); FAIL_IF(state->request_buffer.offset != 0); FAIL_IF(memcmp(state->request_buffer.buffer, request_partial1, sizeof(request_partial1))); /* There should not be a transaction yet. */ FAIL_IF(state->transaction_max != 0); FAIL_IF(DNP3GetTx(state, 0) != NULL); /* Send the second partial. */ SCMutexLock(&flow.m); r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, request_partial2, sizeof(request_partial2)); SCMutexUnlock(&flow.m); FAIL_IF(r != 0); /* The second partial completed the frame, the buffer should now * be clear. */ FAIL_IF(state->request_buffer.len != 0); FAIL_IF(state->request_buffer.offset != 0); /* Should now have a complete transaction. */ tx = DNP3GetTx(state, 0); FAIL_IF(tx == NULL); FAIL_IF(tx->tx_num != 1); FAIL_IF(tx != state->curr); FAIL_IF(tx->buffer == NULL); FAIL_IF(tx->buffer_len != 20); FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE); /* Send partial response. */ SCMutexLock(&flow.m); r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOCLIENT, response_partial1, sizeof(response_partial1)); SCMutexUnlock(&flow.m); FAIL_IF(r != 0); FAIL_IF(state->response_buffer.len != sizeof(response_partial1)); FAIL_IF(state->response_buffer.offset != 0); tx = DNP3GetTx(state, 1); FAIL_IF_NOT_NULL(tx); /* Send rest of response. */ SCMutexLock(&flow.m); r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOCLIENT, response_partial2, sizeof(response_partial2)); SCMutexUnlock(&flow.m); FAIL_IF(r != 0); /* Buffer should now be empty. */ FAIL_IF(state->response_buffer.len != 0); FAIL_IF(state->response_buffer.offset != 0); /* There should now be a response transaction. */ tx = DNP3GetTx(state, 1); FAIL_IF_NULL(tx); FAIL_IF(tx->buffer == NULL); FAIL_IF(tx->buffer_len == 0); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); FLOW_DESTROY(&flow); PASS; } /** * \test Test multiple DNP3 frames in one TCP read. */ static int DNP3ParserTestMultiFrame(void) { DNP3State *state = NULL; /* Unsolicited response 1. */ uint8_t unsol_response1[] = { 0x05, 0x64, 0x16, 0x44, 0x01, 0x00, 0x02, 0x00, 0x89, 0xe5, 0xc4, 0xfa, 0x82, 0x00, 0x00, 0x02, 0x02, 0x17, 0x01, 0x01, 0x81, 0xa7, 0x75, 0xd8, 0x32, 0x4c, 0x81, 0x3e, 0x01, 0xa1, 0xc9, }; /* Unsolicited response 2. */ uint8_t unsol_response2[] = { 0x05, 0x64, 0x16, 0x44, 0x01, 0x00, 0x02, 0x00, 0x89, 0xe5, 0xc5, 0xfb, 0x82, 0x00, 0x00, 0x02, 0x02, 0x17, 0x01, 0x0c, 0x01, 0xd8, 0x75, 0xd8, 0x32, 0x4c, 0xc9, 0x3c, 0x01, 0xa1, 0xc9, }; uint8_t combined[sizeof(unsol_response1) + sizeof(unsol_response2)]; memcpy(combined, unsol_response1, sizeof(unsol_response1)); memcpy(combined + sizeof(unsol_response1), unsol_response2, sizeof(unsol_response2)); /* Setup. */ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); Flow flow; TcpSession ssn; int r; memset(&flow, 0, sizeof(flow)); memset(&ssn, 0, sizeof(ssn)); flow.protoctx = (void *)&ssn; flow.proto = IPPROTO_TCP; flow.alproto = ALPROTO_DNP3; StreamTcpInitConfig(true); SCMutexLock(&flow.m); r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOCLIENT, combined, sizeof(combined)); SCMutexUnlock(&flow.m); FAIL_IF(r != 0); state = flow.alstate; FAIL_IF(state == NULL); FAIL_IF(state->transaction_max != 2); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); FLOW_DESTROY(&flow); PASS; } /** * \test Test the parsing of a request PDU. * * The PDU under test contains a single read request object: * - Group: 1 * - Variation: 0 * - Count: 0 */ static int DNP3ParserTestParsePDU01(void) { /* Frame to be tested. This frame is a DNP3 request with one read * request data object, group 1, variation 0. */ const uint8_t pkt[] = { 0x05, 0x64, 0x0b, 0xc4, 0x17, 0x00, 0xef, 0xff, 0xc4, 0x8f, 0xe1, 0xc8, 0x01, 0x01, 0x00, 0x06, 0x77, 0x6e }; DNP3State *dnp3state = DNP3StateAlloc(NULL, ALPROTO_UNKNOWN); int pdus = DNP3HandleRequestLinkLayer(dnp3state, pkt, sizeof(pkt)); FAIL_IF(pdus < 1); DNP3Transaction *dnp3tx = DNP3GetTx(dnp3state, 0); FAIL_IF_NULL(dnp3tx); FAIL_IF(!dnp3tx->is_request); FAIL_IF(TAILQ_EMPTY(&dnp3tx->objects)); DNP3Object *object = TAILQ_FIRST(&dnp3tx->objects); FAIL_IF(object->group != 1 || object->variation != 0); FAIL_IF(object->count != 0); DNP3StateFree(dnp3state); PASS; } /** * \test Test the decode of a DNP3 fragment with a single 70:3 object. */ static int DNP3ParserDecodeG70V3Test(void) { const uint8_t pkt[] = { 0x05, 0x64, 0x63, 0xc4, 0x04, 0x00, 0x03, 0x00, 0xc7, 0xee, 0xc7, 0xc9, 0x1b, 0x46, 0x03, 0x5b, 0x01, 0x55, 0x00, 0x1a, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x9e, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x1e, 0x00, 0x43, 0x3a, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x2f, 0x44, 0x4e, 0x50, 0x44, 0x65, 0x67, 0x7d, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x93, 0x0c, 0x6e, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, 0x6d, 0x35, 0x20, 0x6f, 0x74, 0x65, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x78, 0x6d, 0x6c, 0xc4, 0x8b }; DNP3State *dnp3state = DNP3StateAlloc(NULL, ALPROTO_UNKNOWN); FAIL_IF_NULL(dnp3state); int bytes = DNP3HandleRequestLinkLayer(dnp3state, pkt, sizeof(pkt)); FAIL_IF(bytes != sizeof(pkt)); DNP3Transaction *tx = DNP3GetTx(dnp3state, 0); FAIL_IF_NULL(tx); FAIL_IF_NOT(tx->is_request); DNP3Object *obj = TAILQ_FIRST(&tx->objects); FAIL_IF_NULL(obj); FAIL_IF_NOT(obj->group == 70); FAIL_IF_NOT(obj->variation == 3); FAIL_IF_NOT(obj->prefix_code == 0x5); FAIL_IF_NOT(obj->range_code == 0xb); FAIL_IF_NOT(obj->count == 1); DNP3Point *point = TAILQ_FIRST(obj->points); FAIL_IF_NULL(point); FAIL_IF_NOT(point->prefix == 85); FAIL_IF_NOT(point->size == 85); FAIL_IF_NULL(point->data); DNP3ObjectG70V3 *data = point->data; FAIL_IF_NOT(strcmp( data->filename, "C:/temp/DNPDeviceConfiguration written to Remote Device.xml") == 0); DNP3StateFree(dnp3state); PASS; } /** * \brief Test that an alert is raised on an unknown object. */ static int DNP3ParserUnknownEventAlertTest(void) { /* Valid DNP3 frame with 70:3 object. */ uint8_t pkt[] = { 0x05, 0x64, 0x63, 0xc4, 0x04, 0x00, 0x03, 0x00, 0xc7, 0xee, 0xc7, 0xc9, 0x1b, /* Object and variation. Originally 70:3, now 70:99, an * unknown object. */ 0x46, 0x63, 0x5b, 0x01, 0x55, 0x00, 0x1a, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x9e, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x1e, 0x00, 0x43, 0x3a, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x2f, 0x44, 0x4e, 0x50, 0x44, 0x65, 0x67, 0x7d, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x93, 0x0c, 0x6e, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, 0x6d, 0x35, 0x20, 0x6f, 0x74, 0x65, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x78, 0x6d, 0x6c, 0xc4, 0x8b }; DNP3FixCrc(pkt + 10, sizeof(pkt) - 10); DNP3State *dnp3state = DNP3StateAlloc(NULL, ALPROTO_UNKNOWN); FAIL_IF_NULL(dnp3state); int bytes = DNP3HandleRequestLinkLayer(dnp3state, pkt, sizeof(pkt)); FAIL_IF(bytes != sizeof(pkt)); DNP3StateFree(dnp3state); PASS; } /** * \brief Test that an alert is raised on incorrect data. */ static int DNP3ParserIncorrectUserData(void) { uint8_t packet_bytes[] = { 0x05, 0x64, 0x08, 0xc4, 0x03, 0x00, 0x04, 0x00, 0xbf, 0xe9, 0xc1, 0xc1, 0x82, 0xc5, 0xee }; AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); Flow flow; TcpSession ssn; memset(&flow, 0, sizeof(flow)); memset(&ssn, 0, sizeof(ssn)); flow.protoctx = (void *)&ssn; flow.proto = IPPROTO_TCP; flow.alproto = ALPROTO_DNP3; StreamTcpInitConfig(true); int r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOCLIENT, packet_bytes, sizeof(packet_bytes)); FAIL_IF(r == 0); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); FLOW_DESTROY(&flow); PASS; } #endif void DNP3ParserRegisterTests(void) { #ifdef UNITTESTS UtRegisterTest("DNP3ParserTestCheckCRC", DNP3ParserTestCheckCRC); UtRegisterTest("DNP3ParserCheckLinkHeaderCRC", DNP3ParserCheckLinkHeaderCRC); UtRegisterTest("DNP3CheckUserDataCRCsTest", DNP3CheckUserDataCRCsTest); UtRegisterTest("DNP3CalculateLinkLengthTest", DNP3CalculateLinkLengthTest); UtRegisterTest("DNP3CalculateTransportLengthWithoutCRCsTest", DNP3CalculateTransportLengthWithoutCRCsTest); UtRegisterTest("DNP3ReassembleApplicationLayerTest01", DNP3ReassembleApplicationLayerTest01); UtRegisterTest("DNP3ProbingParserTest", DNP3ProbingParserTest); UtRegisterTest("DNP3ParserTestRequestResponse", DNP3ParserTestRequestResponse); UtRegisterTest("DNP3ParserTestUnsolicitedResponseConfirm", DNP3ParserTestUnsolicitedResponseConfirm); UtRegisterTest("DNP3ParserTestPartialFrame", DNP3ParserTestPartialFrame); UtRegisterTest("DNP3ParserTestMultiFrame", DNP3ParserTestMultiFrame); UtRegisterTest("DNP3ParserTestFlooded", DNP3ParserTestFlooded); UtRegisterTest("DNP3ParserTestParsePDU01", DNP3ParserTestParsePDU01); UtRegisterTest("DNP3ParserDecodeG70V3Test", DNP3ParserDecodeG70V3Test); UtRegisterTest("DNP3ParserUnknownEventAlertTest", DNP3ParserUnknownEventAlertTest); UtRegisterTest("DNP3ParserIncorrectUserData", DNP3ParserIncorrectUserData); #endif }