From e45e570f59eaecd94b2f8b61a3598ccab77d52e5 Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Tue, 3 Feb 2026 09:45:47 -0500 Subject: [PATCH] doc: describe transform_result and the absent options Document transform_result and both forms of absent, the transforms that can signal an error, how must_error and error_or treat bodies and files inspected in parts, and the rule-load constraints. Note the behavior changes in upgrade.rst: absent's mode string in the analyzer, absent: or_else rejected after a transform that can fail, gunzip and zlib_deflate keeping their input on failure, pcrexform's empty capture, and from_base64's bytes handling. Issue: 7313 Issue: 7114 --- doc/userguide/rules/payload-keywords.rst | 132 +++++++++++++++++++++-- doc/userguide/rules/transforms.rst | 76 +++++++++++++ doc/userguide/upgrade.rst | 28 +++++ 3 files changed, 230 insertions(+), 6 deletions(-) diff --git a/doc/userguide/rules/payload-keywords.rst b/doc/userguide/rules/payload-keywords.rst index 583aea39c3..a05ce1ceff 100644 --- a/doc/userguide/rules/payload-keywords.rst +++ b/doc/userguide/rules/payload-keywords.rst @@ -337,26 +337,146 @@ These differences are also discussed in :doc:`differences-from-snort`. A discussion of this difference can be found at https://redmine.openinfosecfoundation.org/issues/8031 +.. _rules-keyword-absent: + absent ------ -The keyword ``absent`` checks that a sticky buffer does not exist. -It can be used without any argument to match only on absent buffer : +The keyword ``absent`` checks that a sticky buffer does not exist. It takes +either no argument (match only on absent buffer) or ``or_else`` (match on +absent buffer or fall through to subsequent keywords). + +To match on the result of a transform (``must_succeed``, ``must_error``, +``error_or``), see :ref:`rules-keyword-transform-result`. + +absent (no argument) +~~~~~~~~~~~~~~~~~~~~ -Example of ``absent`` in a rule: +Match only when the buffer is absent (NULL). No other keyword is allowed +on the same buffer. .. container:: example-rule alert http any any -> any any (msg:"HTTP request without referer"; :example-rule-emphasis:`http.referer; absent;` sid:1; rev:1;) +absent: or_else +~~~~~~~~~~~~~~~ -It can take an argument "or_else" to match on absent buffer or on what comes next such as negated content, for instance : +Match when the buffer is absent (NULL) OR when subsequent keywords match, +for example a negated content. .. container:: example-rule - alert http any any -> any any (msg:"HTTP request without referer"; :example-rule-emphasis:`http.referer; absent: or_else;` content: !"abc"; sid:1; rev:1;) + alert http any any -> any any (msg:"HTTP request without referer"; \ + :example-rule-emphasis:`http.referer; absent: or_else;` content: !"abc"; sid:1; rev:1;) + +``or_else`` cannot be used on a buffer with a transform that can signal +an error (``from_base64``, ``pcrexform``, ``gunzip``, ``zlib_deflate``); +use ``transform_result: error_or`` instead, which also matches an absent +buffer. + +For files (i.e. ``file.data``), absent means there are no files in the +transaction. + +.. _rules-keyword-transform-result: + +transform_result +---------------- + +``transform_result`` matches on the outcome of a preceding transform. +When a transform that can signal an error cannot complete, it sets an +error flag on the inspection buffer and leaves its input unchanged, so +later transforms and keywords see the data the failed transform received. +When the failing transform is the first on the buffer, that is the +original buffer content. ``transform_result`` reacts to that flag. + +The keyword takes one of ``must_succeed``, ``must_error``, or ``error_or`` +and requires a transform that can signal errors on the same buffer: +``from_base64``, ``pcrexform``, ``gunzip`` or ``zlib_deflate``. See +:ref:`transform-error-signaling`. + +transform_result: must_succeed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Do not match when the transform signals an error, so the keywords after +it only see decoded or extracted data. This keeps a content match from +running against the input bytes after a failed ``from_base64``. Content +after ``must_succeed`` can be the fast pattern. It does not match when the +buffer is absent. + +.. container:: example-rule + + alert http any any -> any any (msg:"Detect malware only in decoded base64"; file.data; \ + from_base64; :example-rule-emphasis:`transform_result: must_succeed;` \ + content:"malware"; sid:1; rev:1;) + +transform_result: must_error +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Match only when the transform signals an error. No other keyword is +allowed on the same buffer, but other sticky buffers can follow to combine +error detection with further matching conditions. It does not match when +the buffer is absent. + +.. container:: example-rule + + alert http any any -> any any (msg:"Detect base64 decode failure"; file.data; \ + from_base64; :example-rule-emphasis:`transform_result: must_error;` sid:1; rev:1;) + +Example combining with a second sticky buffer:: + + alert http any any -> any any (msg:"base64 error and bad host"; \ + http.uri; from_base64: mode strict; transform_result: must_error; \ + http.host; content:"suspicious.example.com"; sid:1;) + +transform_result: error_or +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Match when the transform signals an error, when the buffer is absent, or +when the keywords after it match. It takes the place of ``absent: or_else`` +on a buffer with a transform that can signal an error. + +.. container:: example-rule -For files (i.e ``file.data``), absent means there are no files in the transaction. + alert http any any -> any any (msg:"Detect base64 decode error or malicious content"; \ + file.data; from_base64; :example-rule-emphasis:`transform_result: error_or;` \ + content:"malicious"; sid:1; rev:1;) + +Bodies and files inspected in parts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``file.data``, ``http.response_body`` and ``http.request_body`` inspect a +large body or file in windows and run the transforms on each window. A +window can cut the data where a transform cannot handle it: a strict +``from_base64`` decode fails on a window that does not start on a 4-byte +boundary. ``must_error`` and ``error_or`` therefore only react to an error +when the whole body or file is inspected at once. ``must_succeed`` still +keeps content from matching a window whose transform failed. + +Rule-writing constraints +~~~~~~~~~~~~~~~~~~~~~~~~ + +The following constraints apply to both ``absent`` and +``transform_result`` and are enforced at rule-load time: + +* The keyword must come first on its buffer, after the sticky buffer + keyword and any transforms. A transform placed after the keyword starts + a new buffer; with ``absent: or_else`` or ``transform_result`` the rule + then fails to load. +* Only one ``absent`` or ``transform_result`` is allowed per buffer, and + the two cannot be mixed on the same buffer. +* ``fast_pattern`` cannot be used on a buffer that carries ``absent``, + ``transform_result: must_error`` or ``transform_result: error_or``. +* ``absent`` and ``transform_result`` do not work on frame buffers. +* ``transform_result`` requires a transform that can signal errors on the + same buffer. +* ``absent: or_else`` cannot be used on such a buffer; use + ``transform_result: error_or`` instead. +* ``absent: or_else``, ``transform_result: error_or`` and + ``transform_result: must_succeed`` need at least one other keyword after + them on the buffer. +* ``must_error`` cannot be combined with other keywords on the same + buffer, but other sticky buffers can follow. bsize ----- diff --git a/doc/userguide/rules/transforms.rst b/doc/userguide/rules/transforms.rst index ae16aafed2..6d8bd81950 100644 --- a/doc/userguide/rules/transforms.rst +++ b/doc/userguide/rules/transforms.rst @@ -167,6 +167,48 @@ on. alert http any any -> any any (http_request_line; to_sha256; \ content:"\|54A9 7A8A B09C 1B81 3725 2214 51D3 F997 F015 9DD7 049E E5AD CED3 945A FC79 7401\|"; sid:1;) +.. _transform-error-signaling: + +Error-Signaling Transforms +-------------------------- + +Some transforms can signal an error when they cannot complete their operation. The transform +then sets an error flag on the inspection buffer and leaves its input unchanged, so later +transforms and keywords see the data the failed transform received. When the failing +transform is the first on the buffer, that is the original buffer content. + +The following transforms can signal errors. Each also signals one when it cannot get memory +for its result. + +- :ref:`pcrexform ` signals an error when the regular expression does not match + or its capture group cannot be extracted. An empty buffer is left as it is, and a capture + of nothing leaves an empty buffer; neither is an error. +- :ref:`from_base64 ` signals an error when a non-empty input is not decoded: + ``offset`` is at or past the end of the buffer, ``bytes`` is larger than the input left + after ``offset``, or decoding produces nothing. Invalid bytes in the input are not an error + in ``rfc2045`` mode, which skips them, or in ``rfc4648`` mode when something decodes before + them; in ``strict`` mode they are. +- :ref:`gunzip ` and :ref:`zlib_deflate ` signal an error when the + input does not decompress. + +The ``transform_result`` keyword matches on the transform outcome: + +- ``transform_result: error_or`` matches if the transform signals an error, if the buffer + is absent, or if the keywords after it match. +- ``transform_result: must_error`` matches only when the transform signals an error. No + other keyword is allowed on the same buffer. It does not match when the buffer is absent. +- ``transform_result: must_succeed`` does not match when the transform signals an error, so + the keywords after it only see decoded or extracted data. It does not match when the + buffer is absent. + +On ``file.data``, ``http.response_body`` and ``http.request_body``, a large body or file is +inspected in windows, and ``must_error`` and ``error_or`` only react to an error when the +whole body or file is inspected at once. + +See :ref:`rules-keyword-transform-result` for full documentation and examples of each option. + +.. _pcrexform: + pcrexform --------- @@ -174,6 +216,9 @@ Takes the buffer, applies the required regular expression, and outputs the *firs .. note:: this transform requires a mandatory option string containing a regular expression. +If the regular expression does not match the buffer content, the transform signals an error +and leaves its input unchanged. See :ref:`transform-error-signaling` for how to +detect or guard against transform failures using ``transform_result``. This example alerts if ``http.request_line`` contains ``/dropper.php``: @@ -182,6 +227,20 @@ This example alerts if ``http.request_line`` contains ``/dropper.php``: alert http any any -> any any (msg:"HTTP with pcrexform"; http.request_line; \ pcrexform:"[a-zA-Z]+\s+(.*)\s+HTTP"; content:"/dropper.php"; sid:1;) +This example relies on the input being kept when ``pcrexform`` fails. It reduces a name with +a subdomain to its last two labels (``www.example.com`` becomes ``example.com``); a name with +no subdomain does not match the expression and is kept as it is. Either way the buffer is +exactly ``example.com``:: + + alert dns any any -> any any (msg:"example.com or a subdomain of it"; \ + dns.query; pcrexform:"\.([^\.]+\.[^\.]+)$"; content:"example.com"; \ + startswith; endswith; sid:1;) + +This example alerts if ``pcrexform`` fails to match (e.g., unexpected request line format):: + + alert http any any -> any any (msg:"pcrexform match failure"; http.request_line; \ + pcrexform:"[a-zA-Z]+\s+(.*)\s+HTTP"; transform_result: must_error; sid:2;) + url_decode ---------- @@ -303,6 +362,13 @@ line breaks, and any non base64 alphabet. Mode ``strict`` will fail if an invalid character is found in the encoded bytes. +.. note:: When a non-empty input is not decoded, ``from_base64`` signals an error and leaves + its input unchanged. That happens when ``offset`` is at or past the end + of the buffer, when ``bytes`` is larger than the input left after ``offset``, or when + decoding produces nothing (for example, ``strict`` mode rejecting a character outside the + base64 alphabet). See :ref:`transform-error-signaling` for how to detect or guard + against decode failures using ``transform_result``. + The following examples will alert when the buffer contents match (see the last ``content`` value for the expected strings). @@ -431,6 +497,8 @@ the buffer. return string.upper(sub), bytes end +.. _gunzip: + gunzip ------ @@ -444,6 +512,9 @@ If the decompressed data were to be larger than max-size, the transform will decompress data up to max-size. Value 0 is forbidden for max-size (there is no unlimited value). +If the input is not gzip data, the transform signals an error and leaves its input +unchanged. See :ref:`transform-error-signaling`. + This example alerts if ``http.uri`` contains base64-encoded gzipped value: .. container:: example-rule @@ -454,6 +525,8 @@ This example alerts if ``http.uri`` contains base64-encoded gzipped value: gunzip: max-size 1024; content:"This is compressed then base64-encoded"; startswith; endswith; sid:2; rev:1;) +.. _zlib_deflate: + zlib_deflate ------------ @@ -467,6 +540,9 @@ If the decompressed data were to be larger than max-size, the transform will decompress data up to max-size. Value 0 is forbidden for max-size (there is no unlimited value). +If the input is not zlib data, the transform signals an error and leaves its input +unchanged. See :ref:`transform-error-signaling`. + This example alerts if ``http.uri`` contains base64-encoded zlib-compressed value: .. container:: example-rule diff --git a/doc/userguide/upgrade.rst b/doc/userguide/upgrade.rst index 1673d0db8a..0a566c3eee 100644 --- a/doc/userguide/upgrade.rst +++ b/doc/userguide/upgrade.rst @@ -156,6 +156,26 @@ Keyword Changes See :doc:`rules/ssh-keywords` for the full state and failure semantics. +- ``absent: or_else`` is now rejected on a buffer whose transform can signal + an error (``from_base64``, ``pcrexform``, ``gunzip``, ``zlib_deflate``), + and such a rule fails to load. Use ``transform_result: error_or`` instead: + it matches when the buffer is absent, when the transform fails, or when + the keywords after it match. See :ref:`rules-keyword-transform-result`. + +- ``gunzip`` and ``zlib_deflate`` keep their input when it does not + decompress, and signal an error that ``transform_result`` can match on. + 8.0 left an empty buffer, so ``bsize:0`` after a failed decompression no + longer matches; use ``transform_result: must_error`` instead. + +- ``pcrexform`` leaves an empty buffer when its capture group matches + nothing. 8.0 could leave the original buffer in place in that case. + +- ``from_base64`` decodes the input when ``bytes`` equals the input left after + ``offset``; 8.0 left such a buffer undecoded. When ``offset`` is at or past + the end of the buffer, or ``bytes`` is larger than the input left after + ``offset``, the buffer is still left undecoded and the transform now signals + an error that ``transform_result`` can match on. + Other Changes ~~~~~~~~~~~~~ - ``engine-analysis`` output has been changed for keywords that now use the generic integers framework @@ -165,6 +185,14 @@ Other Changes - tcp.seq - tcp.window +- Engine analysis (``rules.json``): the ``absent`` object emitted per keyword + changed from ``{"or_else": true|false}`` to ``{"mode": "only|or_else"}``. + The new ``transform_result`` keyword emits + ``{"transform_result": {"mode": "error_or|must_error|must_succeed"}}`` + under the same per-keyword object. Consumers reading the boolean ``or_else`` + field should switch to the string ``mode`` field, and recognize both the + ``absent`` and ``transform_result`` objects. + - ``dnp3`` has reduced the maximum number of open transactions from 500 down to 32, the maximum number of points per message from unbounded to 16384, and the maximum number of objects per message