Skip to content

detect: guard rate filter callback registration#15380

Open
kenifor wants to merge 1 commit into
OISF:mainfrom
kenifor:fix-rate-filter-callback-null-deref
Open

detect: guard rate filter callback registration#15380
kenifor wants to merge 1 commit into
OISF:mainfrom
kenifor:fix-rate-filter-callback-null-deref

Conversation

@kenifor
Copy link
Copy Markdown

@kenifor kenifor commented May 13, 2026

Ticket: 8560

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/8560

Describe changes:

  • Guard SCDetectEngineRegisterRateFilterCallback() against a NULL return from DetectEngineGetCurrent().
  • Avoid a NULL pointer dereference when this public extension API is called while no suitable current detect engine is available.
  • This API is documented in the devguide and used by the bundled library examples, so callers should not be able to crash Suricata due to a missing internal detect-engine context.

Provide values to any of the below to override the defaults.

  • To use a Suricata-Verify or Suricata-Update pull request,
    link to the pull request in the respective _BRANCH variable.
  • Leave unused overrides blank or remove.

SV_REPO=
SV_BRANCH=
SU_REPO=
SU_BRANCH=

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
@kenifor kenifor requested a review from victorjulien as a code owner May 13, 2026 11:08
Comment thread src/detect-engine.c
void SCDetectEngineRegisterRateFilterCallback(SCDetectRateFilterFunc fn, void *arg)
{
DetectEngineCtx *de_ctx = DetectEngineGetCurrent();
if (de_ctx == NULL) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually a scenario a plugin could end up in?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if it can, where should a plugin attempt to reregister these callbacks again?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Change the return type to bool/int so 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.

  2. 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.

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.

4 participants