Security #8980
opendnp3: framing errors stop transaction inspection for the flow
Description
- Summary
Certain malformed DNP3 link-layer input can cause Suricata to stop producing
DNP3 transaction records for the remainder of the TCP connection.
When a stream begins with a frame containing a bad link-header CRC, no DNP3
record is emitted for that frame or for subsequent well-formed frames on the
same connection.
A related result occurs when a valid frame is followed by non-frame garbage:
the opening frame is reported, but subsequent valid frames are not.
- Affected versions
This was reproduced on:
- Suricata 7.0.17
- Suricata 8.0.3
- Suricata 8.0.6
- Impact
DNP3-over-TCP sessions are commonly long-lived. A monitor that starts or
restarts may encounter a connection that will not perform another TCP
handshake for an extended period.
If parsing stops after the first boundary-aligned malformed frame, later DNP3
activity on that connection is not represented by DNP3 transaction records or
transaction-aware inspection.
The malformed frame itself is detected internally, but when it precedes the
first transaction its decoder event is also discarded.
- Sentinel reproducer
The attached sentinel archive contains four captures. Each scenario is
followed by three additional well-formed DNP3 frames, each in its own TCP
segment and using distinct addresses.
Observed results:
| Capture | Sentinels reported | Total DNP3 records |
| --- | ---: | ---: |
| Two valid frames, baseline | 3 of 3 | 5 |
| Bad link-header CRC, then valid frames | 0 of 3 | 0 |
| Bad data-block CRC, then valid frames | 3 of 3 | 4 |
| Valid frame, garbage, then valid frames | 0 of 3 | 1 |
The bad data-block CRC case demonstrates that the loss is defect-specific and
that the parser can recover from some link-layer errors.
The valid-frame-then-garbage case demonstrates that the loss is not solely
caused by the malformed frame being first on the connection.
- Midstream scenario
With `stream.midstream` enabled, a boundary-aligned pickup of an established
connection reproduces the condition.
On 8.0.6, the midstream stream containing a valid frame before the defect
alerts, while the defect-first stream does not. The latter is detected as
DNP3, reaches the parser, logs the discarded-event warning, and produces no
alert.
Pickups beginning 5, 8, or 16 bytes into a frame instead fail protocol
detection. Those cases are outside this finding. The relevant scenario is a
pickup on a DNP3 frame boundary.
- Attacker model and limits
An attacker must be able to place bytes in the application stream of a
DNP3/TCP connection, either as an endpoint or from an on-path position capable
of injecting sequence-correct TCP data.
No DNP3 endpoint implementation was tested. It has therefore not been
demonstrated that a particular endpoint will discard the malformed frame,
remain connected, and process the subsequent valid frames. The demonstrated
impact is the loss of DNP3 transaction reporting within Suricata.
Expected behavior is for Suricata to report the malformed input and safely
resume inspection of subsequent valid DNP3 frames where resynchronization is
possible.
- Disclosure coordination
This behavior is described in an academic paper currently under submission.
Publication, if accepted, is not expected before December 2026. Coordination
or delay is available if required.
Files
JI Updated by Jason Ish 2 days ago
- Related to Security #8979: dnp3: link-layer events before first transaction are dropped added
OT Updated by OISF Ticketbot 2 days ago
- Subtask #8981 added
OT Updated by OISF Ticketbot 2 days ago
- Label deleted (
Needs backport to 8.0)
PA Updated by Philippe Antoine 35 minutes ago
- Severity set to LOW
I propose LOW severity as an evasion on disabled-by-default DNP3
JI Updated by Jason Ish 25 minutes ago
- Private changed from Yes to No
JI Updated by Jason Ish 19 minutes ago
AI review caught this,
Medium — src/app-layer-dnp3.c:1112 and src/app-layer-dnp3.c:1246: Invalid start bytes still terminate parsing, so the valid-frame → garbage → valid-frame reproducer from Redmine #8980 continues to suppress subsequent DNP3 inspection on long-lived flows. suricata-verify MR !178 does not cover this scenario. Resynchronize established DNP3 streams after non-frame garbage and add the ticket’s exact sequence as a regression test.
However, after internal discussion we're keeping this behavior.