From a7b077aa17b49e311599677c3d99adb4e562163e Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Wed, 29 Nov 2023 17:27:29 +0100 Subject: [PATCH] signatures: Support custom event via [event_name] syntax This change allows to specify a per signature specific event, overriding the default signature_match event. It further removes the message parameter from such events if not provided in the signature. This also tracks the message as StringValPtr directly to avoid allocating the same StringVal for every DoAction() call. Closes #3403 --- NEWS | 35 ++++++++++++ src/RuleAction.cc | 55 +++++++++++++++++-- src/RuleAction.h | 17 ++++-- src/rule-parse.y | 6 ++ .../signatures.custom-event-errors/.stderr | 8 +++ .../Baseline/signatures.custom-event/.stderr | 1 + .../Baseline/signatures.custom-event/id.out | 1 + .../Baseline/signatures.custom-event/out | 5 ++ .../btest/signatures/custom-event-errors.zeek | 33 +++++++++++ testing/btest/signatures/custom-event.zeek | 51 +++++++++++++++++ 10 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 testing/btest/Baseline/signatures.custom-event-errors/.stderr create mode 100644 testing/btest/Baseline/signatures.custom-event/.stderr create mode 100644 testing/btest/Baseline/signatures.custom-event/id.out create mode 100644 testing/btest/Baseline/signatures.custom-event/out create mode 100644 testing/btest/signatures/custom-event-errors.zeek create mode 100644 testing/btest/signatures/custom-event.zeek diff --git a/NEWS b/NEWS index 07e6af35b6..7877c33c19 100644 --- a/NEWS +++ b/NEWS @@ -69,6 +69,41 @@ New Functionality Given this is the first iteration of this feature, feedback around usability and use-cases that aren't covered are more than welcome. +- The event keyword in signatures was extended to support choosing a custom event + to raise instead of ``signature_match()``. This can be more efficient in certain + scenarios compared to funneling every match through a single event. + + The new syntax is to put the name of the event in brackets before the string + or identifier used as message. As an extension, it is possible to only provide + the bracketed event name. In this case, the framework expects the event's + parameters to consist of only state and data as follows: + + signature only-event { + payload /.*root/ + event [found_root] + } + + event found_root(state: signature_state, data: string) { } + + Passing an additional message parameter to a custom event is possible with the + following syntax. The custom event's parameters need to align with those for the + ``signature_match()` event: + + signature event-with-msg { + payload /.*root/ + event [found_root_with_msg] "the-message" + } + + event found_root_with_msg(state: signature_state, msg: string, data: string) { } + + The message can also be specified as a Zeek side identifier, in which case + its initial value will be passed to the custom events. This is identical + to the behavior with the default ``signature_match()`` event. + + Note that matches for signatures with custom events will not be recorded in + ``signatures.log``. This log is based on the generation of ``signature_match()`` + events. + Changed Functionality --------------------- diff --git a/src/RuleAction.cc b/src/RuleAction.cc index d3fae9fea8..d181c00b79 100644 --- a/src/RuleAction.cc +++ b/src/RuleAction.cc @@ -15,16 +15,59 @@ using std::string; namespace zeek::detail { -RuleActionEvent::RuleActionEvent(const char* arg_msg) { msg = util::copy_string(arg_msg); } +RuleActionEvent::RuleActionEvent(const char* arg_msg) + : msg(make_intrusive(arg_msg)), handler(signature_match) {} +RuleActionEvent::RuleActionEvent(const char* arg_msg, const char* event_name) { + if ( arg_msg ) // Message can be null (not provided). + msg = make_intrusive(arg_msg); + + handler = zeek::event_registry->Lookup(event_name); + + if ( ! handler ) { + reporter->Error("unknown event '%s' specified in rule", event_name); + return; + } + + // Register non-script usage to make the UsageAnalyzer happy. + zeek::event_registry->Register(event_name, false /*is_from_script*/); + + static const auto& signature_match_params = signature_match->GetFunc()->GetType()->ParamList()->GetTypes(); + // Fabricated params for non-message event(state: signature_state, data: string) + static const std::vector signature_match2_params = {signature_match_params[0], + signature_match_params[2]}; + + if ( msg ) { + // If msg was provided, the function signature needs to agree with + // the signature_match event, even if it's a different event. + if ( ! handler->GetFunc()->GetType()->CheckArgs(signature_match_params, true, true) ) + zeek::reporter->Error("wrong event parameters for '%s'", event_name); + } + else { + // When no message is provided, use non-message parameters. + if ( ! handler->GetFunc()->GetType()->CheckArgs(signature_match2_params, true, true) ) + zeek::reporter->Error("wrong event parameters for '%s'", event_name); + } +} void RuleActionEvent::DoAction(const Rule* parent, RuleEndpointState* state, const u_char* data, int len) { - if ( signature_match ) - event_mgr.Enqueue(signature_match, IntrusivePtr{AdoptRef{}, rule_matcher->BuildRuleStateValue(parent, state)}, - make_intrusive(msg), - data ? make_intrusive(len, (const char*)data) : val_mgr->EmptyString()); + if ( handler ) { + zeek::Args args; + args.reserve(msg ? 3 : 2); + args.push_back({AdoptRef{}, rule_matcher->BuildRuleStateValue(parent, state)}); + if ( msg ) + args.push_back(msg); + if ( data ) + args.push_back(make_intrusive(len, reinterpret_cast(data))); + else + args.push_back(zeek::val_mgr->EmptyString()); + + event_mgr.Enqueue(handler, std::move(args)); + } } -void RuleActionEvent::PrintDebug() { fprintf(stderr, " RuleActionEvent: |%s|\n", msg); } +void RuleActionEvent::PrintDebug() { + fprintf(stderr, " RuleActionEvent: |%s (%s)|\n", msg ? msg->CheckString() : "", handler->Name()); +} RuleActionMIME::RuleActionMIME(const char* arg_mime, int arg_strength) { mime = util::copy_string(arg_mime); diff --git a/src/RuleAction.h b/src/RuleAction.h index 94e6f0ef98..dab891d6fe 100644 --- a/src/RuleAction.h +++ b/src/RuleAction.h @@ -3,9 +3,16 @@ #include // for u_char #include +#include "zeek/EventHandler.h" +#include "zeek/IntrusivePtr.h" #include "zeek/Tag.h" -namespace zeek::detail { +namespace zeek { + +class StringVal; +using StringValPtr = IntrusivePtr; + +namespace detail { class Rule; class RuleEndpointState; @@ -24,14 +31,15 @@ public: class RuleActionEvent : public RuleAction { public: explicit RuleActionEvent(const char* arg_msg); - ~RuleActionEvent() override { delete[] msg; } + explicit RuleActionEvent(const char* arg_msg, const char* event_name); void DoAction(const Rule* parent, RuleEndpointState* state, const u_char* data, int len) override; void PrintDebug() override; private: - const char* msg; + StringValPtr msg; + EventHandlerPtr handler; }; class RuleActionMIME : public RuleAction { @@ -88,4 +96,5 @@ public: void PrintDebug() override; }; -} // namespace zeek::detail +} // namespace detail +} // namespace zeek diff --git a/src/rule-parse.y b/src/rule-parse.y index f95c3b9fd5..a4b85d21a8 100644 --- a/src/rule-parse.y +++ b/src/rule-parse.y @@ -191,6 +191,12 @@ rule_attr: (zeek::detail::RuleHdrTest::Comp) $2, $3)); } + | TOK_EVENT '[' TOK_IDENT ']' + { current_rule->AddAction(new zeek::detail::RuleActionEvent(nullptr, $3)); } + + | TOK_EVENT '[' TOK_IDENT ']' string + { current_rule->AddAction(new zeek::detail::RuleActionEvent($5, $3)); } + | TOK_EVENT string { current_rule->AddAction(new zeek::detail::RuleActionEvent($2)); } diff --git a/testing/btest/Baseline/signatures.custom-event-errors/.stderr b/testing/btest/Baseline/signatures.custom-event-errors/.stderr new file mode 100644 index 0000000000..64eb04875e --- /dev/null +++ b/testing/btest/Baseline/signatures.custom-event-errors/.stderr @@ -0,0 +1,8 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +warning in <...>/custom-event-errors.zeek, line 7: Wrong number of arguments for function. Expected 3, got 2. (event(state:signature_state, data:string)) +error: wrong event parameters for 'wrong_signature2' +warning in <...>/custom-event-errors.zeek, line 9: Wrong number of arguments for function. Expected 2, got 3. (event(state:signature_state, msg:string, data:string)) +error: wrong event parameters for 'wrong_signature3' +warning in <...>/custom-event-errors.zeek, line 11: Type mismatch in function argument #1. Expected string, got count. (event(state:signature_state, msg:count, data:string)) +error: wrong event parameters for 'wrong_signature4' +error: unknown event 'non_existing_event' specified in rule diff --git a/testing/btest/Baseline/signatures.custom-event/.stderr b/testing/btest/Baseline/signatures.custom-event/.stderr new file mode 100644 index 0000000000..49d861c74c --- /dev/null +++ b/testing/btest/Baseline/signatures.custom-event/.stderr @@ -0,0 +1 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. diff --git a/testing/btest/Baseline/signatures.custom-event/id.out b/testing/btest/Baseline/signatures.custom-event/id.out new file mode 100644 index 0000000000..49d861c74c --- /dev/null +++ b/testing/btest/Baseline/signatures.custom-event/id.out @@ -0,0 +1 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. diff --git a/testing/btest/Baseline/signatures.custom-event/out b/testing/btest/Baseline/signatures.custom-event/out new file mode 100644 index 0000000000..6305bd639e --- /dev/null +++ b/testing/btest/Baseline/signatures.custom-event/out @@ -0,0 +1,5 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +signature_match2 [orig_h=127.0.0.1, orig_p=30000/udp, resp_h=127.0.0.1, resp_p=13000/udp] +signature_match [orig_h=127.0.0.1, orig_p=30000/udp, resp_h=127.0.0.1, resp_p=13000/udp] - message from identifier (cannot be changed) +signature_match3 [orig_h=127.0.0.1, orig_p=30000/udp, resp_h=127.0.0.1, resp_p=13000/udp] - message from identifier (cannot be changed) +signature_match3 [orig_h=127.0.0.1, orig_p=30000/udp, resp_h=127.0.0.1, resp_p=13000/udp] - message diff --git a/testing/btest/signatures/custom-event-errors.zeek b/testing/btest/signatures/custom-event-errors.zeek new file mode 100644 index 0000000000..cf4700c1da --- /dev/null +++ b/testing/btest/signatures/custom-event-errors.zeek @@ -0,0 +1,33 @@ +# @TEST-DOC: Using the wrong paramters for custom signature events. +# +# @TEST-EXEC-FAIL: zeek -b -s id -r $TRACES/chksums/ip4-udp-good-chksum.pcap %INPUT >id.out +# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff .stderr + +@TEST-START-FILE id.sig +signature udp-proto { + ip-proto == 17 + event [wrong_signature2] "id" +} + +signature udp-proto2 { + ip-proto == 17 + event [wrong_signature3] +} + +signature udp-proto3 { + ip-proto == 17 + event [wrong_signature4] "not a count" +} + +signature udp-proto4 { + ip-proto == 17 + event [non_existing_event] +} + +@TEST-END-FILE + +event wrong_signature2(state: signature_state, data: string) { } + +event wrong_signature3(state: signature_state, msg: string, data: string) { } + +event wrong_signature4(state: signature_state, msg: count, data: string) { } diff --git a/testing/btest/signatures/custom-event.zeek b/testing/btest/signatures/custom-event.zeek new file mode 100644 index 0000000000..7aa5c5a15f --- /dev/null +++ b/testing/btest/signatures/custom-event.zeek @@ -0,0 +1,51 @@ +# @TEST-DOC: Test the [event_name] notation within the event keyword of rules. +# +# @TEST-EXEC: zeek -b -s id -r $TRACES/chksums/ip4-udp-good-chksum.pcap %INPUT >out +# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff .stderr +# @TEST-EXEC: btest-diff out + +@TEST-START-FILE id.sig +signature udp-proto { + ip-proto == 17 + event [my_signature_match3] "message" +} + +signature udp-proto-msg-id { + ip-proto == 17 + event [my_signature_match3] message_as_id +} + +signature udp-proto-msg-id2 { + ip-proto == 17 + event message_as_id +} + +signature udp-stuff { + dst-ip == mynets + event [my_signature_match2] +} + +@TEST-END-FILE + +const message_as_id = "message from identifier (cannot be changed)"; + +const mynets: set[subnet] = { + 192.168.1.0/24, + 10.0.0.0/8, + 127.0.0.0/24 +}; + +event signature_match(state: signature_state, msg: string, data: string) + { + print fmt("signature_match %s - %s", state$conn$id, msg); + } + +event my_signature_match2(state: signature_state, data: string) + { + print fmt("signature_match2 %s", state$conn$id); + } + +event my_signature_match3(state: signature_state, msg: string, data: string) + { + print fmt("signature_match3 %s - %s", state$conn$id, msg); + }