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.
Finding: HTTP Perl async callbacks store unrefcounted
SV/CVpointersSummary
In freenginx, the HTTP Perl module stores asynchronous callbacks in
ctx->nextusing borrowed Perl referents fromSvRV()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.xssrc/http/modules/perl/ngx_http_perl_module.chas_request_body(),sleep(), andngx_http_perl_handle_request()Source Evidence
The XS bindings store the callback referent directly:
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:
The call path ultimately invokes:
There is no
SvREFCNT_inc()when storingctx->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:
$r->sleep()callback$r->sleep()callback$r->sleep()plus closure churn$r->has_request_body()callback$r->has_request_body()callbackThe same behavior reproduced in plain and sanitizer-enabled builds. The observed log symptom for the stale callback path was:
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.