From 172204be619588e98af5fe2fc3c052cdf7135352 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Wed, 12 Aug 2026 16:56:20 -0600 Subject: [PATCH] dnp3: allocate variable-length object fields dynamically Introduce a new code generated type, dynchararray. Use this new dynamically allocated char array instead of using a 65535 static array. Ticket: #8827 --- scripts/dnp3-gen/dnp3-gen.py | 41 +++-- scripts/dnp3-gen/dnp3-objects.yaml | 20 +-- src/app-layer-dnp3-objects.c | 240 +++++++++++++++++++++-------- src/app-layer-dnp3-objects.h | 20 +-- src/app-layer-dnp3.c | 108 +++++++++++++ 5 files changed, 337 insertions(+), 92 deletions(-) diff --git a/scripts/dnp3-gen/dnp3-gen.py b/scripts/dnp3-gen/dnp3-gen.py index 251e24d49c..266d32d8db 100755 --- a/scripts/dnp3-gen/dnp3-gen.py +++ b/scripts/dnp3-gen/dnp3-gen.py @@ -85,7 +85,7 @@ void DNP3PushPoint(lua_State *luastate, DNP3Object *object, lua_pushliteral(luastate, "{{field.name}}"); lua_pushnumber(luastate, data->{{field.name}}); lua_settable(luastate, -3); -{% elif field["type"] == "chararray" %} +{% elif field["type"] in ["chararray", "dynchararray"] %} lua_pushliteral(luastate, "{{field.name}}"); LuaPushStringBuffer(luastate, (uint8_t *)data->{{field.name}}, strlen(data->{{field.name}})); @@ -169,7 +169,7 @@ void OutputJsonDNP3SetItem(SCJsonBuilder *js, DNP3Object *object, SCJbSetBase64(js, "data->{{field.name}}", data->{{field.name}}, data->{{field.len_field}}); {% elif field.type == "vstr4" %} SCJbSetString(js, "data->{{field.name}}", data->{{field.name}}); -{% elif field.type == "chararray" %} +{% elif field.type in ["chararray", "dynchararray"] %} if (data->{{field.len_field}} > 0) { SCJbSetStringFromBytes( js, "{{field.name}}", (const uint8_t *)data->{{field.name}}, data->{{field.len_field}}); @@ -199,11 +199,8 @@ void OutputJsonDNP3SetItem(SCJsonBuilder *js, DNP3Object *object, """ def has_freeable_types(fields): - freeable_types = [ - "bytearray", - ] for field in fields: - if field["type"] in freeable_types: + if field["type"] in ["bytearray", "dynchararray"]: return True return False @@ -284,6 +281,8 @@ typedef struct DNP3ObjectG{{object.group}}V{{object.variation}}_ { uint64_t {{field.name}}; {% elif field.type == "bytearray" %} uint8_t *{{field.name}}; +{% elif field.type == "dynchararray" %} + char *{{field.name}}; {% elif field.type == "vstr4" %} char {{field.name}}[5]; {% elif field.type == "chararray" %} @@ -505,12 +504,36 @@ static int DNP3DecodeObjectG{{object.group}}V{{object.variation}}(const uint8_t *buf += object->{{field.len_field}}; *len -= object->{{field.len_field}}; } +{% elif field.type == "dynchararray" %} +{% if field.len_from_prefix %} + if (prefix - (offset - *len) >= {{field.size}} || prefix < (offset - *len)) { + goto error; + } +{% if field.size <= 256 %} + object->{{field.len_field}} = (uint8_t)(prefix - (offset - *len)); +{% else %} + object->{{field.len_field}} = (uint16_t)(prefix - (offset - *len)); +{% endif %} +{% endif %} + if (*len < object->{{field.len_field}}) { + goto error; + } + object->{{field.name}} = SCMalloc((size_t)object->{{field.len_field}} + 1); + if (unlikely(object->{{field.name}} == NULL)) { + goto error; + } + if (object->{{field.len_field}} > 0) { + memcpy(object->{{field.name}}, *buf, object->{{field.len_field}}); + } + object->{{field.name}}[object->{{field.len_field}}] = '\\\\0'; + *buf += object->{{field.len_field}}; + *len -= object->{{field.len_field}}; {% elif field.type == "chararray" %} {% if field.len_from_prefix %} if (prefix - (offset - *len) >= {{field.size}} || prefix < (offset - *len)) { goto error; } -{% if field.size == 255 %} +{% if field.size <= 256 %} object->{{field.len_field}} = (uint8_t)(prefix - (offset - *len)); {% else %} object->{{field.len_field}} = (uint16_t)(prefix - (offset - *len)); @@ -566,7 +589,7 @@ static int DNP3DecodeObjectG{{object.group}}V{{object.variation}}(const uint8_t error: if (object != NULL) { {% for field in object.fields %} -{% if field.type == "bytearray" %} +{% if field.type in ["bytearray", "dynchararray"] %} if (object->{{field.name}} != NULL) { SCFree(object->{{field.name}}); } @@ -589,7 +612,7 @@ void DNP3FreeObjectPoint(int group, int variation, void *point) case DNP3_OBJECT_CODE({{object.group}}, {{object.variation}}): { DNP3ObjectG{{object.group}}V{{object.variation}} *object = (DNP3ObjectG{{object.group}}V{{object.variation}} *)point; {% for field in object.fields %} -{% if field.type == "bytearray" %} +{% if field.type in ["bytearray", "dynchararray"] %} if (object->{{field.name}} != NULL) { SCFree(object->{{field.name}}); } diff --git a/scripts/dnp3-gen/dnp3-objects.yaml b/scripts/dnp3-gen/dnp3-objects.yaml index 7b4ab3677a..e8fd64750e 100644 --- a/scripts/dnp3-gen/dnp3-objects.yaml +++ b/scripts/dnp3-gen/dnp3-objects.yaml @@ -2341,13 +2341,13 @@ objects: name: file_function_code - type: uint8 name: status_code - - type: chararray + - type: dynchararray name: filename size: 65536 len_field: filename_size - type: uint16 name: data_size - - type: chararray + - type: dynchararray name: data size: 65536 len_field: data_size @@ -2365,11 +2365,11 @@ objects: name: password_size - type: uint32 name: authentication_key - - type: chararray + - type: dynchararray name: username size: 65536 len_field: username_size - - type: chararray + - type: dynchararray name: password size: 65536 len_field: password_size @@ -2396,7 +2396,7 @@ objects: name: maximum_block_size - type: uint16 name: request_id - - type: chararray + - type: dynchararray name: filename size: 65536 len_field: filename_size @@ -2484,7 +2484,7 @@ objects: name: permissions - type: uint16 name: request_id - - type: chararray + - type: dynchararray name: filename size: 65536 len_field: filename_size @@ -2498,7 +2498,7 @@ objects: - type: uint16 name: file_specification_len fields: - - type: chararray + - type: dynchararray name: file_specification size: 65536 len_from_prefix: true @@ -2762,7 +2762,7 @@ objects: name: error_code - type: dnp3time name: time_of_error - - type: chararray + - type: dynchararray name: error_text size: 65536 len_from_prefix: true @@ -2820,7 +2820,7 @@ objects: name: user_public_key_len - type: uint16 name: certification_data_len - - type: chararray + - type: dynchararray name: username size: 65536 len_field: username_len @@ -2843,7 +2843,7 @@ objects: name: username_len - type: uint16 name: master_challenge_data_len - - type: chararray + - type: dynchararray size: 65536 name: username len_field: username_len diff --git a/src/app-layer-dnp3-objects.c b/src/app-layer-dnp3-objects.c index 7f38d418a1..bcb07cb703 100644 --- a/src/app-layer-dnp3-objects.c +++ b/src/app-layer-dnp3-objects.c @@ -6793,29 +6793,35 @@ static int DNP3DecodeObjectG70V1(const uint8_t **buf, uint16_t *len, uint8_t pre if (!DNP3ReadUint8(buf, len, &object->status_code)) { goto error; } + if (*len < object->filename_size) { + goto error; + } + object->filename = SCMalloc((size_t)object->filename_size + 1); + if (unlikely(object->filename == NULL)) { + goto error; + } if (object->filename_size > 0) { - if (*len < object->filename_size) { - /* Not enough data. */ - goto error; - } memcpy(object->filename, *buf, object->filename_size); - *buf += object->filename_size; - *len -= object->filename_size; } object->filename[object->filename_size] = '\0'; + *buf += object->filename_size; + *len -= object->filename_size; if (!DNP3ReadUint16(buf, len, &object->data_size)) { goto error; } + if (*len < object->data_size) { + goto error; + } + object->data = SCMalloc((size_t)object->data_size + 1); + if (unlikely(object->data == NULL)) { + goto error; + } if (object->data_size > 0) { - if (*len < object->data_size) { - /* Not enough data. */ - goto error; - } memcpy(object->data, *buf, object->data_size); - *buf += object->data_size; - *len -= object->data_size; } object->data[object->data_size] = '\0'; + *buf += object->data_size; + *len -= object->data_size; if (!DNP3AddPoint(points, object, point_index, prefix_code, prefix)) { goto error; @@ -6828,6 +6834,12 @@ static int DNP3DecodeObjectG70V1(const uint8_t **buf, uint16_t *len, uint8_t pre return 1; error: if (object != NULL) { + if (object->filename != NULL) { + SCFree(object->filename); + } + if (object->data != NULL) { + SCFree(object->data); + } SCFree(object); } @@ -6870,26 +6882,32 @@ static int DNP3DecodeObjectG70V2(const uint8_t **buf, uint16_t *len, uint8_t pre if (!DNP3ReadUint32(buf, len, &object->authentication_key)) { goto error; } + if (*len < object->username_size) { + goto error; + } + object->username = SCMalloc((size_t)object->username_size + 1); + if (unlikely(object->username == NULL)) { + goto error; + } if (object->username_size > 0) { - if (*len < object->username_size) { - /* Not enough data. */ - goto error; - } memcpy(object->username, *buf, object->username_size); - *buf += object->username_size; - *len -= object->username_size; } object->username[object->username_size] = '\0'; + *buf += object->username_size; + *len -= object->username_size; + if (*len < object->password_size) { + goto error; + } + object->password = SCMalloc((size_t)object->password_size + 1); + if (unlikely(object->password == NULL)) { + goto error; + } if (object->password_size > 0) { - if (*len < object->password_size) { - /* Not enough data. */ - goto error; - } memcpy(object->password, *buf, object->password_size); - *buf += object->password_size; - *len -= object->password_size; } object->password[object->password_size] = '\0'; + *buf += object->password_size; + *len -= object->password_size; if (!DNP3AddPoint(points, object, point_index, prefix_code, prefix)) { goto error; @@ -6902,6 +6920,12 @@ static int DNP3DecodeObjectG70V2(const uint8_t **buf, uint16_t *len, uint8_t pre return 1; error: if (object != NULL) { + if (object->username != NULL) { + SCFree(object->username); + } + if (object->password != NULL) { + SCFree(object->password); + } SCFree(object); } @@ -6956,16 +6980,19 @@ static int DNP3DecodeObjectG70V3(const uint8_t **buf, uint16_t *len, uint8_t pre if (!DNP3ReadUint16(buf, len, &object->request_id)) { goto error; } + if (*len < object->filename_size) { + goto error; + } + object->filename = SCMalloc((size_t)object->filename_size + 1); + if (unlikely(object->filename == NULL)) { + goto error; + } if (object->filename_size > 0) { - if (*len < object->filename_size) { - /* Not enough data. */ - goto error; - } memcpy(object->filename, *buf, object->filename_size); - *buf += object->filename_size; - *len -= object->filename_size; } object->filename[object->filename_size] = '\0'; + *buf += object->filename_size; + *len -= object->filename_size; if (!DNP3AddPoint(points, object, point_index, prefix_code, prefix)) { goto error; @@ -6978,6 +7005,9 @@ static int DNP3DecodeObjectG70V3(const uint8_t **buf, uint16_t *len, uint8_t pre return 1; error: if (object != NULL) { + if (object->filename != NULL) { + SCFree(object->filename); + } SCFree(object); } @@ -7030,7 +7060,7 @@ static int DNP3DecodeObjectG70V4(const uint8_t **buf, uint16_t *len, uint8_t pre if (prefix - (offset - *len) >= 256 || prefix < (offset - *len)) { goto error; } - object->optional_text_len = (uint16_t)(prefix - (offset - *len)); + object->optional_text_len = (uint8_t)(prefix - (offset - *len)); if (object->optional_text_len > 0) { if (*len < object->optional_text_len) { /* Not enough data. */ @@ -7096,7 +7126,7 @@ static int DNP3DecodeObjectG70V5(const uint8_t **buf, uint16_t *len, uint8_t pre if (prefix - (offset - *len) >= 256 || prefix < (offset - *len)) { goto error; } - object->file_data_len = (uint16_t)(prefix - (offset - *len)); + object->file_data_len = (uint8_t)(prefix - (offset - *len)); if (object->file_data_len > 0) { if (*len < object->file_data_len) { /* Not enough data. */ @@ -7165,7 +7195,7 @@ static int DNP3DecodeObjectG70V6(const uint8_t **buf, uint16_t *len, uint8_t pre if (prefix - (offset - *len) >= 256 || prefix < (offset - *len)) { goto error; } - object->optional_text_len = (uint16_t)(prefix - (offset - *len)); + object->optional_text_len = (uint8_t)(prefix - (offset - *len)); if (object->optional_text_len > 0) { if (*len < object->optional_text_len) { /* Not enough data. */ @@ -7236,16 +7266,19 @@ static int DNP3DecodeObjectG70V7(const uint8_t **buf, uint16_t *len, uint8_t pre if (!DNP3ReadUint16(buf, len, &object->request_id)) { goto error; } + if (*len < object->filename_size) { + goto error; + } + object->filename = SCMalloc((size_t)object->filename_size + 1); + if (unlikely(object->filename == NULL)) { + goto error; + } if (object->filename_size > 0) { - if (*len < object->filename_size) { - /* Not enough data. */ - goto error; - } memcpy(object->filename, *buf, object->filename_size); - *buf += object->filename_size; - *len -= object->filename_size; } object->filename[object->filename_size] = '\0'; + *buf += object->filename_size; + *len -= object->filename_size; if (!DNP3AddPoint(points, object, point_index, prefix_code, prefix)) { goto error; @@ -7258,6 +7291,9 @@ static int DNP3DecodeObjectG70V7(const uint8_t **buf, uint16_t *len, uint8_t pre return 1; error: if (object != NULL) { + if (object->filename != NULL) { + SCFree(object->filename); + } SCFree(object); } @@ -7296,16 +7332,19 @@ static int DNP3DecodeObjectG70V8(const uint8_t **buf, uint16_t *len, uint8_t pre goto error; } object->file_specification_len = (uint16_t)(prefix - (offset - *len)); + if (*len < object->file_specification_len) { + goto error; + } + object->file_specification = SCMalloc((size_t)object->file_specification_len + 1); + if (unlikely(object->file_specification == NULL)) { + goto error; + } if (object->file_specification_len > 0) { - if (*len < object->file_specification_len) { - /* Not enough data. */ - goto error; - } memcpy(object->file_specification, *buf, object->file_specification_len); - *buf += object->file_specification_len; - *len -= object->file_specification_len; } object->file_specification[object->file_specification_len] = '\0'; + *buf += object->file_specification_len; + *len -= object->file_specification_len; if (!DNP3AddPoint(points, object, point_index, prefix_code, prefix)) { goto error; @@ -7318,6 +7357,9 @@ static int DNP3DecodeObjectG70V8(const uint8_t **buf, uint16_t *len, uint8_t pre return 1; error: if (object != NULL) { + if (object->file_specification != NULL) { + SCFree(object->file_specification); + } SCFree(object); } @@ -8044,16 +8086,19 @@ static int DNP3DecodeObjectG120V7(const uint8_t **buf, uint16_t *len, uint8_t pr goto error; } object->error_text_len = (uint16_t)(prefix - (offset - *len)); + if (*len < object->error_text_len) { + goto error; + } + object->error_text = SCMalloc((size_t)object->error_text_len + 1); + if (unlikely(object->error_text == NULL)) { + goto error; + } if (object->error_text_len > 0) { - if (*len < object->error_text_len) { - /* Not enough data. */ - goto error; - } memcpy(object->error_text, *buf, object->error_text_len); - *buf += object->error_text_len; - *len -= object->error_text_len; } object->error_text[object->error_text_len] = '\0'; + *buf += object->error_text_len; + *len -= object->error_text_len; if (!DNP3AddPoint(points, object, point_index, prefix_code, prefix)) { goto error; @@ -8066,6 +8111,9 @@ static int DNP3DecodeObjectG120V7(const uint8_t **buf, uint16_t *len, uint8_t pr return 1; error: if (object != NULL) { + if (object->error_text != NULL) { + SCFree(object->error_text); + } SCFree(object); } @@ -8255,16 +8303,19 @@ static int DNP3DecodeObjectG120V10(const uint8_t **buf, uint16_t *len, uint8_t p if (!DNP3ReadUint16(buf, len, &object->certification_data_len)) { goto error; } + if (*len < object->username_len) { + goto error; + } + object->username = SCMalloc((size_t)object->username_len + 1); + if (unlikely(object->username == NULL)) { + goto error; + } if (object->username_len > 0) { - if (*len < object->username_len) { - /* Not enough data. */ - goto error; - } memcpy(object->username, *buf, object->username_len); - *buf += object->username_len; - *len -= object->username_len; } object->username[object->username_len] = '\0'; + *buf += object->username_len; + *len -= object->username_len; if (object->user_public_key_len > 0) { if (*len < object->user_public_key_len) { /* Not enough data. */ @@ -8303,6 +8354,9 @@ static int DNP3DecodeObjectG120V10(const uint8_t **buf, uint16_t *len, uint8_t p return 1; error: if (object != NULL) { + if (object->username != NULL) { + SCFree(object->username); + } if (object->user_public_key != NULL) { SCFree(object->user_public_key); } @@ -8349,16 +8403,19 @@ static int DNP3DecodeObjectG120V11(const uint8_t **buf, uint16_t *len, uint8_t p if (!DNP3ReadUint16(buf, len, &object->master_challenge_data_len)) { goto error; } + if (*len < object->username_len) { + goto error; + } + object->username = SCMalloc((size_t)object->username_len + 1); + if (unlikely(object->username == NULL)) { + goto error; + } if (object->username_len > 0) { - if (*len < object->username_len) { - /* Not enough data. */ - goto error; - } memcpy(object->username, *buf, object->username_len); - *buf += object->username_len; - *len -= object->username_len; } object->username[object->username_len] = '\0'; + *buf += object->username_len; + *len -= object->username_len; if (object->master_challenge_data_len > 0) { if (*len < object->master_challenge_data_len) { /* Not enough data. */ @@ -8384,6 +8441,9 @@ static int DNP3DecodeObjectG120V11(const uint8_t **buf, uint16_t *len, uint8_t p return 1; error: if (object != NULL) { + if (object->username != NULL) { + SCFree(object->username); + } if (object->master_challenge_data != NULL) { SCFree(object->master_challenge_data); } @@ -8845,6 +8905,47 @@ error: void DNP3FreeObjectPoint(int group, int variation, void *point) { switch(DNP3_OBJECT_CODE(group, variation)) { + case DNP3_OBJECT_CODE(70, 1): { + DNP3ObjectG70V1 *object = (DNP3ObjectG70V1 *)point; + if (object->filename != NULL) { + SCFree(object->filename); + } + if (object->data != NULL) { + SCFree(object->data); + } + break; + } + case DNP3_OBJECT_CODE(70, 2): { + DNP3ObjectG70V2 *object = (DNP3ObjectG70V2 *)point; + if (object->username != NULL) { + SCFree(object->username); + } + if (object->password != NULL) { + SCFree(object->password); + } + break; + } + case DNP3_OBJECT_CODE(70, 3): { + DNP3ObjectG70V3 *object = (DNP3ObjectG70V3 *)point; + if (object->filename != NULL) { + SCFree(object->filename); + } + break; + } + case DNP3_OBJECT_CODE(70, 7): { + DNP3ObjectG70V7 *object = (DNP3ObjectG70V7 *)point; + if (object->filename != NULL) { + SCFree(object->filename); + } + break; + } + case DNP3_OBJECT_CODE(70, 8): { + DNP3ObjectG70V8 *object = (DNP3ObjectG70V8 *)point; + if (object->file_specification != NULL) { + SCFree(object->file_specification); + } + break; + } case DNP3_OBJECT_CODE(83, 1): { DNP3ObjectG83V1 *object = (DNP3ObjectG83V1 *)point; if (object->data_objects != NULL) { @@ -8883,6 +8984,13 @@ void DNP3FreeObjectPoint(int group, int variation, void *point) } break; } + case DNP3_OBJECT_CODE(120, 7): { + DNP3ObjectG120V7 *object = (DNP3ObjectG120V7 *)point; + if (object->error_text != NULL) { + SCFree(object->error_text); + } + break; + } case DNP3_OBJECT_CODE(120, 8): { DNP3ObjectG120V8 *object = (DNP3ObjectG120V8 *)point; if (object->certificate != NULL) { @@ -8899,6 +9007,9 @@ void DNP3FreeObjectPoint(int group, int variation, void *point) } case DNP3_OBJECT_CODE(120, 10): { DNP3ObjectG120V10 *object = (DNP3ObjectG120V10 *)point; + if (object->username != NULL) { + SCFree(object->username); + } if (object->user_public_key != NULL) { SCFree(object->user_public_key); } @@ -8909,6 +9020,9 @@ void DNP3FreeObjectPoint(int group, int variation, void *point) } case DNP3_OBJECT_CODE(120, 11): { DNP3ObjectG120V11 *object = (DNP3ObjectG120V11 *)point; + if (object->username != NULL) { + SCFree(object->username); + } if (object->master_challenge_data != NULL) { SCFree(object->master_challenge_data); } diff --git a/src/app-layer-dnp3-objects.h b/src/app-layer-dnp3-objects.h index 39561e6606..71b9eeb668 100644 --- a/src/app-layer-dnp3-objects.h +++ b/src/app-layer-dnp3-objects.h @@ -1199,9 +1199,9 @@ typedef struct DNP3ObjectG70V1_ { uint32_t group_id; uint8_t file_function_code; uint8_t status_code; - char filename[65536]; + char *filename; uint16_t data_size; - char data[65536]; + char *data; } DNP3ObjectG70V1; typedef struct DNP3ObjectG70V2_ { @@ -1210,8 +1210,8 @@ typedef struct DNP3ObjectG70V2_ { uint16_t password_offset; uint16_t password_size; uint32_t authentication_key; - char username[65536]; - char password[65536]; + char *username; + char *password; } DNP3ObjectG70V2; typedef struct DNP3ObjectG70V3_ { @@ -1224,7 +1224,7 @@ typedef struct DNP3ObjectG70V3_ { uint16_t operational_mode; uint16_t maximum_block_size; uint16_t request_id; - char filename[65536]; + char *filename; } DNP3ObjectG70V3; typedef struct DNP3ObjectG70V4_ { @@ -1260,11 +1260,11 @@ typedef struct DNP3ObjectG70V7_ { uint64_t created_timestamp; uint16_t permissions; uint16_t request_id; - char filename[65536]; + char *filename; } DNP3ObjectG70V7; typedef struct DNP3ObjectG70V8_ { - char file_specification[65536]; + char *file_specification; uint16_t file_specification_len; } DNP3ObjectG70V8; @@ -1351,7 +1351,7 @@ typedef struct DNP3ObjectG120V7_ { uint16_t association_id; uint8_t error_code; uint64_t time_of_error; - char error_text[65536]; + char *error_text; uint16_t error_text_len; } DNP3ObjectG120V7; @@ -1376,7 +1376,7 @@ typedef struct DNP3ObjectG120V10_ { uint16_t username_len; uint16_t user_public_key_len; uint16_t certification_data_len; - char username[65536]; + char *username; uint8_t *user_public_key; uint8_t *certification_data; } DNP3ObjectG120V10; @@ -1385,7 +1385,7 @@ typedef struct DNP3ObjectG120V11_ { uint8_t key_change_method; uint16_t username_len; uint16_t master_challenge_data_len; - char username[65536]; + char *username; uint8_t *master_challenge_data; } DNP3ObjectG120V11; diff --git a/src/app-layer-dnp3.c b/src/app-layer-dnp3.c index 3a2311535d..f68296ffc6 100644 --- a/src/app-layer-dnp3.c +++ b/src/app-layer-dnp3.c @@ -2546,6 +2546,111 @@ static int DNP3ParserTestParsePDU01(void) PASS; } +/** + * \test Ensure variable-length DNP3 objects do not embed maximum-sized buffers. + */ +static int DNP3ParserObjectStructSizeTest(void) +{ + const size_t max_point_size = 1024; + + FAIL_IF(sizeof(DNP3ObjectG70V1) >= max_point_size); + FAIL_IF(sizeof(DNP3ObjectG70V2) >= max_point_size); + FAIL_IF(sizeof(DNP3ObjectG70V3) >= max_point_size); + FAIL_IF(sizeof(DNP3ObjectG70V7) >= max_point_size); + FAIL_IF(sizeof(DNP3ObjectG70V8) >= max_point_size); + FAIL_IF(sizeof(DNP3ObjectG120V7) >= max_point_size); + FAIL_IF(sizeof(DNP3ObjectG120V10) >= max_point_size); + FAIL_IF(sizeof(DNP3ObjectG120V11) >= max_point_size); + + PASS; +} + +/** + * \test Decode non-empty dynamically allocated G70V2 strings. + */ +static int DNP3ParserDecodeG70V2Test(void) +{ + const uint8_t input[] = { + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x78, + 0x56, + 0x34, + 0x12, + 'b', + 'o', + 'b', + 's', + 'e', + 'c', + 'r', + 'e', + 't', + }; + const uint8_t *buf = input; + uint16_t len = sizeof(input); + DNP3PointList *points = DNP3PointListAlloc(); + FAIL_IF_NULL(points); + + int event = DNP3DecodeObject(70, 2, &buf, &len, 0, 0, 1, points); + FAIL_IF(event != 0); + FAIL_IF(len != 0); + DNP3Point *point = TAILQ_FIRST(points); + FAIL_IF_NULL(point); + DNP3ObjectG70V2 *data = point->data; + FAIL_IF_NULL(data); + FAIL_IF(strcmp(data->username, "bob") != 0); + FAIL_IF(strcmp(data->password, "secret") != 0); + FAIL_IF(data->authentication_key != 0x12345678); + + DNP3FreeObjectPointList(70, 2, points); + PASS; +} + +/** + * \test Clean up an allocated G70V2 username when the password is truncated. + */ +static int DNP3ParserDecodeG70V2TruncatedTest(void) +{ + const uint8_t input[] = { + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x78, + 0x56, + 0x34, + 0x12, + 'b', + 'o', + 'b', + 's', + 'e', + 'c', + }; + const uint8_t *buf = input; + uint16_t len = sizeof(input); + DNP3PointList *points = DNP3PointListAlloc(); + FAIL_IF_NULL(points); + + int event = DNP3DecodeObject(70, 2, &buf, &len, 0, 0, 1, points); + FAIL_IF(event != DNP3_DECODER_EVENT_MALFORMED); + FAIL_IF_NOT(TAILQ_EMPTY(points)); + + DNP3FreeObjectPointList(70, 2, points); + PASS; +} + /** * \test Test the decode of a DNP3 fragment with a single 70:3 object. */ @@ -2694,6 +2799,9 @@ void DNP3ParserRegisterTests(void) UtRegisterTest("DNP3ParserTestMultiFrame", DNP3ParserTestMultiFrame); UtRegisterTest("DNP3ParserTestFlooded", DNP3ParserTestFlooded); UtRegisterTest("DNP3ParserTestParsePDU01", DNP3ParserTestParsePDU01); + UtRegisterTest("DNP3ParserObjectStructSizeTest", DNP3ParserObjectStructSizeTest); + UtRegisterTest("DNP3ParserDecodeG70V2Test", DNP3ParserDecodeG70V2Test); + UtRegisterTest("DNP3ParserDecodeG70V2TruncatedTest", DNP3ParserDecodeG70V2TruncatedTest); UtRegisterTest("DNP3ParserDecodeG70V3Test", DNP3ParserDecodeG70V3Test); UtRegisterTest("DNP3ParserUnknownEventAlertTest", DNP3ParserUnknownEventAlertTest);