dnp3: reduce flood threshold to 32 and make configurable

Lower the number of unreplied requests from 500 to 32 to consider a
flood. At the very least this is an anomaly given the DNP3 spec mentions
that DNP3 should only have one outstanding request at a time, with an
exception for unsolicited responses, so in practice no more than 2
should be seen.

Additionally make this value configurable by introducing the max-tx
parameter.

Ticket: #8181
(cherry picked from commit a16f087b93)
pull/14600/head
Jason Ish 4 months ago committed by Shivani Bhardwaj
parent 63225d5f8e
commit 635af8dc8b

@ -34,6 +34,16 @@ also check all the new features that have been added but are not covered by
this guide. Those features are either not enabled by default or require
dedicated new configuration.
Upgrading to 7.0.14
-------------------
Other Changes
~~~~~~~~~~~~~
- ``dnp3`` has reduced the default maximum number of outstanding
transactions from 500 down to 32. A ``max-tx`` parameter has been
added to the ``dnp3`` parser for users that need a larger number of
in-flight transactions.
Upgrading to 7.0.12
-------------------

@ -40,9 +40,6 @@
#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. */
@ -93,6 +90,14 @@ enum {
/* Extract the range code from the object qualifier. */
#define DNP3_OBJ_RANGE(x) (x & 0xf)
/* Default number of unreplied requests to be considered a flood.
*
* DNP3 is a request/response SCADA protocol with typically only 1-2
* transactions in flight. But set a limit high enough to allow for
* some pipelining but reduce the chance of memory exhaustion
* attacks. */
static uint64_t dnp3_max_tx = 32;
/* Decoder event map. */
SCEnumCharMap dnp3_decoder_event_table[] = {
{"FLOODED", DNP3_DECODER_EVENT_FLOODED},
@ -514,7 +519,7 @@ static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3, bool request)
TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next);
/* Check for flood state. */
if (dnp3->unreplied > DNP3_DEFAULT_REQ_FLOOD_COUNT) {
if (dnp3->unreplied > dnp3_max_tx && !dnp3->flooded) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_FLOODED);
dnp3->flooded = 1;
}
@ -1386,7 +1391,7 @@ static void DNP3StateTxFree(void *state, uint64_t tx_id)
dnp3->unreplied--;
/* Check flood state. */
if (dnp3->flooded && dnp3->unreplied < DNP3_DEFAULT_REQ_FLOOD_COUNT) {
if (dnp3->flooded && dnp3->unreplied < dnp3_max_tx) {
dnp3->flooded = 0;
}
@ -1432,8 +1437,7 @@ static int DNP3GetAlstateProgress(void *tx, uint8_t direction)
int retval = 0;
/* If flooded, "ack" old transactions. */
if (dnp3->flooded && (dnp3->transaction_max -
dnp3tx->tx_num >= DNP3_DEFAULT_REQ_FLOOD_COUNT)) {
if (dnp3->flooded && (dnp3->transaction_max - dnp3tx->tx_num >= dnp3_max_tx)) {
SCLogDebug("flooded: returning tx as done.");
SCReturnInt(1);
}
@ -1606,8 +1610,13 @@ void RegisterDNP3Parsers(void)
AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3,
DNP3GetTxData);
AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetStateData);
}
else {
/* Parse max-tx configuration. */
intmax_t value = 0;
if (ConfGetInt("app-layer.protocols.dnp3.max-tx", &value)) {
dnp3_max_tx = (uint64_t)value;
}
} else {
SCLogConfig("Parser disabled for protocol %s. "
"Protocol detection still on.", proto_name);
}
@ -2254,7 +2263,7 @@ static int DNP3ParserTestFlooded(void)
FAIL_IF_NOT(tx->done);
FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER));
for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) {
for (uint64_t i = 0; i < dnp3_max_tx - 1; i++) {
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, request, sizeof(request)));

@ -1179,6 +1179,7 @@ app-layer:
enabled: no
detection-ports:
dp: 20000
#max-tx: 32
# SCADA EtherNet/IP and CIP protocol support
enip:

Loading…
Cancel
Save