Skip to content

modbus: add per-value detection keywords - ticket 8131 v5 - #16245

Open
akekulip wants to merge 6 commits into
OISF:mainfrom
akekulip:modbus-keywords-8131-v5
Open

akekulip wants to merge 6 commits into
OISF:mainfrom
akekulip:modbus-keywords-8131-v5

Conversation

@akekulip

@akekulip akekulip commented Sep 21, 2026

Copy link
Copy Markdown

Make sure these boxes are checked accordingly before submitting your Pull Request -- thank you.

Contribution style:

Our Contribution agreements:

Changes (if applicable):

Link to ticket: https://redmine.openinfosecfoundation.org/issues/8131

Previous PR: #16108

SV_BRANCH=OISF/suricata-verify#3260

Changes since v4:

  • modbus.function, modbus.subfunction and modbus.exception_code now accept code names as
    well as numbers, as requested in review
  • moved the note about address offsets into the two address keyword sections
  • v4 set SV_REPO to a pull request URL. Only SV_BRANCH parses one, so both prepare-deps
    jobs failed and every build, suricata-verify and schema job was skipped. v4 had no CI.
  • rebased on current main. v4 was 88 commits behind, and commit-check walks
    git rev-list --reverse origin/main..., a symmetric difference, so it tried to build 93
    revisions and was cancelled at the 6 hour limit. This branch keeps that walk to 6.

Describe changes:

  • add per-value uint keywords for fields already in the EVE modbus log: modbus.unit_id,
    modbus.transaction_id, modbus.protocol_id, modbus.function, modbus.subfunction,
    modbus.exception_code, modbus.read.address, modbus.read.quantity, modbus.write.address,
    modbus.write.quantity and modbus.write.value
  • same pattern as the enip keywords: registered from Rust, ranges and negation supported,
    request matched to-server and response to-client; the legacy modbus keyword is unchanged
  • modbus.function, modbus.subfunction and modbus.exception_code also accept the names
    logged in function_code, diagnostic.code and exception.code
  • the enums use the UPPERCASE string style, not the default snake_case, because the modbus
    logger prints the sawp variant names rather than EnumString output. With snake_case the
    log would say RdCoils and the rule rd_coils. This differs from dnp3.func, snmp.pdu_type
    and ntp.mode, so say if you would rather have snake_case
  • the sawp catch-all variants are not accepted as names. FunctionCode::Unknown is 44,
    ExceptionCode::Unknown is 12 and DiagnosticSubfunction::Reserved is 22, all real codes.
    Those values still match numerically
  • modbus.function matches function_raw, and sawp derives the name from raw ^ ERROR_MASK.
    An error response to function 8 logs function_raw 136 with function_code Diagnostic, so
    a name matches the request but not its error response, where >127 or 136 does. This is
    documented. Masking the bit would break modbus.function:>127
  • EnumStringU16 did not register the suricata attribute, unlike EnumStringU8 and
    EnumStringU32, and modbus.subfunction is a u16. Fixed in the first commit
  • multiple-write responses echo a quantity where single writes echo a value, so
    modbus.write.quantity matches that echo and modbus.write.value only matches single
    writes; documented, along with the difference from the 1-based addressing of the legacy
    keyword
  • schema: map the new keywords to their EVE fields, add the write.quantity, write.and_mask
    and write.or_mask properties and allow string data in multiple-write requests, which the
    modbus logger already emits

@github-actions

Copy link
Copy Markdown

NOTE: This PR may contain new authors.

The EnumStringU8 and EnumStringU32 derives both register the suricata
attribute, but EnumStringU16 does not, so a u16 enumeration cannot
select a string style even though stringenum.rs handles every width
the same way. Register the attribute for u16 as well.

Ticket: OISF#8131.
Add uint keywords for the Modbus MBAP header values already present
in the EVE log: modbus.unit_id, modbus.transaction_id and
modbus.protocol_id.

They follow the ENIP keyword pattern: uint matching with ranges and
negation, inspecting the request in to-server direction and the
response in to-client direction.

Ticket: OISF#8131.
Add uint keywords for the Modbus function-level values already present
in the EVE log: modbus.function (function code), modbus.subfunction
(diagnostic subfunction code) and modbus.exception_code (exception
code in error responses).

They match numerically, with ranges and negation, and also accept the
code names that the EVE log prints in function_code, diagnostic.code
and exception.code, so a name read from a log can be used as is in a
rule. Names are matched case insensitively and can be negated.

The catch-all variants of the sawp enumerations are deliberately left
out of the names: sawp assigns them to any value it cannot decode, but
their discriminants collide with real codes, so accepting them by name
would match something other than what the name says.

Error responses carry the request function code with its highest bit
set, while the logged name is derived from the code without that bit.
A name therefore does not match an error response; the numeric form
does.

Ticket: OISF#8131.
Add uint keywords for the Modbus data-access values already present in
the EVE log: modbus.read.address, modbus.read.quantity,
modbus.write.address, modbus.write.quantity and modbus.write.value.

Read keywords match read request headers. Write keywords also match
the echoed values in write responses, following what is logged.
Multiple-write responses echo a quantity where single-write responses
echo a value, so modbus.write.quantity matches that echo and
modbus.write.value only matches single writes.

Ticket: OISF#8131.
Document modbus.unit_id, modbus.transaction_id, modbus.protocol_id,
modbus.function, modbus.subfunction, modbus.exception_code,
modbus.read.address, modbus.read.quantity, modbus.write.address,
modbus.write.quantity and modbus.write.value, naming the EVE field
each one is taken from and how error responses and write response
echoes are matched.

List the names accepted by modbus.function, modbus.subfunction and
modbus.exception_code. Say that the names logged for codes that have
none are not accepted, as they stand for more than one value, and that
a name does not match the error response of a request, since the
logged name drops the error bit that the matched value carries.

Note how the address keywords differ from the 1-based addressing of
the modbus keyword in the sections of the two keywords concerned,
rather than once for the whole set.

Ticket: OISF#8131.
Map the new modbus per-value keywords to the EVE fields they match.

Also add the write.quantity, write.and_mask and write.or_mask
properties and allow string data in multiple-write requests, matching
what the modbus logger already emits, so that alerts embedding such
transactions validate against the schema.

Ticket: OISF#8131.
@github-actions

Copy link
Copy Markdown

NOTE: This PR may contain new authors.

@akekulip
akekulip marked this pull request as ready for review September 21, 2026 15:32
@akekulip
akekulip requested review from a team, jasonish and jufajardini as code owners September 21, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant