detect: guard rate filter callback registration#15380
Conversation
SCDetectEngineRegisterRateFilterCallback() is a public extension API documented in the devguide and used by the bundled library examples. It calls DetectEngineGetCurrent() and stores the callback in the returned DetectEngineCtx. DetectEngineGetCurrent() can return NULL if the master detect-engine list does not contain a suitable current engine. In that case the current code dereferences NULL, and the external caller cannot recover from this internal state before the dereference. Return early with an error log when no current detect engine is available. Ticket: 8560
| void SCDetectEngineRegisterRateFilterCallback(SCDetectRateFilterFunc fn, void *arg) | ||
| { | ||
| DetectEngineCtx *de_ctx = DetectEngineGetCurrent(); | ||
| if (de_ctx == NULL) { |
There was a problem hiding this comment.
I'm really curious how we would get here with a NULL de_ctx. The only call site:
if (de_ctx->RateFilterCallback && original_action != pa->action) {
pa->action = de_ctx->RateFilterCallback(p, sid, gid, rev, original_action,
pa->action, de_ctx->rate_filter_callback_arg);So there a detect engine when this is called. It's also a full engine as this is only called on a signature match AFAICS.
There was a problem hiding this comment.
It is also called in plugins see
examples/lib/custom/main.c: SCDetectEngineRegisterRateFilterCallback(RateFilterCallback, NULL);
examples/lib/live/main.c: SCDetectEngineRegisterRateFilterCallback(RateFilterCallback, NULL);
cc @jasonish
There was a problem hiding this comment.
Is this actually a scenario a plugin could end up in?
There was a problem hiding this comment.
And if it can, where should a plugin attempt to reregister these callbacks again?
There was a problem hiding this comment.
Yes. SCDetectEngineRegisterRateFilterCallback() is a documented public
extension API. A plugin author following the bundled examples
(examples/lib/custom/main.c, examples/lib/live/main.c) will naturally
call it during plugin/library initialization — before DetectEngineCtxInit()
has run and registered an engine as current. In that window,
DetectEngineGetCurrent() returns NULL and the original code dereferences it
unconditionally. In multi-tenant or delayed-detect configurations the window
is even wider.
The runtime call-site that @victorjulien points to (inside the signature-match
path) is indeed safe, because there a live engine is guaranteed. But this
function is also a registration entry-point called by external code that has
no visibility into the engine lifecycle — that is the path Svace flags.
Where should a plugin reregister?
That is a fair follow-up. The current fix (guard + log + early return) prevents
the crash, but since the function is void the caller has no way to detect
that registration was silently dropped.
Two options, in order of invasiveness:
-
Change the return type to
bool/intso the caller knows the
registration failed and can retry after the engine is ready. This is a
small API change but requires updating the devguide and the two bundled
examples. -
Keep
void, document the constraint — add a note to the devguide
stating that this API must be called after the detect engine is initialized.
The guard then acts purely as a defensive belt-and-suspenders measure.
Ticket: 8560
Make sure these boxes are checked accordingly before submitting your Pull Request -- thank you.
Contribution style:
https://docs.suricata.io/en/latest/devguide/contributing/contribution-process.html
Our Contribution agreements:
https://suricata.io/about/contribution-agreement/ (note: this is only required once)
Changes (if applicable):
(including schema descriptions)
https://redmine.openinfosecfoundation.org/projects/suricata/issues
Link to ticket: https://redmine.openinfosecfoundation.org/issues/8560
Describe changes:
SCDetectEngineRegisterRateFilterCallback()against a NULL return fromDetectEngineGetCurrent().Provide values to any of the below to override the defaults.
link to the pull request in the respective
_BRANCHvariable.SV_REPO=
SV_BRANCH=
SU_REPO=
SU_BRANCH=