Skip to content

HTTP Perl async callbacks store unrefcounted SV/CV pointers #26

@xintenseapple

Description

@xintenseapple

Finding: HTTP Perl async callbacks store unrefcounted SV/CV pointers

Summary

In freenginx, the HTTP Perl module stores asynchronous callbacks in ctx->next using borrowed Perl referents from SvRV() without incrementing their Perl reference counts. For anonymous callbacks passed to $r->sleep() or $r->has_request_body(), Perl can release the referent before freenginx later dispatches the callback.

Affected Area

  • src/http/modules/perl/nginx.xs
  • src/http/modules/perl/ngx_http_perl_module.c
  • Functions: has_request_body(), sleep(), and ngx_http_perl_handle_request()

Source Evidence

The XS bindings store the callback referent directly:

ctx->next = SvRV(ST(1));  /* has_request_body() */
ctx->next = SvRV(ST(2));  /* sleep() */

The request object is kept alive across the asynchronous wait, but the Perl callback referent is not. Later, freenginx consumes the stored pointer and passes it to Perl:

if (ctx->next == NULL) {
    ...
} else {
    sub = ctx->next;
    handler = &ngx_null_name;
    ctx->next = NULL;
}

rc = ngx_http_perl_call_handler(aTHX_ r, ctx, pmcf->nginx, sub, NULL,
                                handler, NULL);

The call path ultimately invokes:

n = call_sv(sub, G_EVAL);

There is no SvREFCNT_inc() when storing ctx->next, and no ownership handoff that keeps anonymous callback referents alive across the asynchronous gap.

Runtime Evidence

A local harness compared named callbacks with captured anonymous callbacks:

Scenario Result
named $r->sleep() callback response succeeds
captured anonymous $r->sleep() callback callback fails after async wait
captured anonymous $r->sleep() plus closure churn callback fails after async wait
named $r->has_request_body() callback response succeeds
captured anonymous $r->has_request_body() callback returns HTTP 500
captured anonymous body callback plus closure churn returns HTTP 500

The same behavior reproduced in plain and sanitizer-enabled builds. The observed log symptom for the stale callback path was:

call_sv("") failed: "Can't use an undefined value as a subroutine reference."

No worker memory-corruption crash was observed in this harness, but the root cause is still a C/Perl ownership violation: a raw Perl callback pointer is retained across async execution without a Perl reference.

Expected Behavior

A callback stored for later asynchronous dispatch should own a Perl reference until it is dispatched, canceled, or cleaned up with the request.

Impact

A remote request to a configured HTTP Perl endpoint can trigger invalid stale callback use and request failure. The demonstrated impact is response failure/HTTP 500 on affected handlers; more severe behavior may depend on Perl allocator reuse and callback lifetime details.

Suggested Fix

Validate that the callback argument is a code reference, increment its Perl refcount before storing it in ctx->next, and decrement it exactly once after callback dispatch, cancellation, or request cleanup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions