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); + }