Skip to content

Commit 6335e76

Browse files
committed
bugfix: prevent SIGSEGV in receiveuntil __gc on aborted multipart upload.
A client that POSTs a multipart body and aborts the connection mid-pattern against ngx.req.socket():receiveuntil(boundary) can SIGSEGV the worker from the LuaJIT GC's __gc finalizer on the compiled-pattern userdata. ngx_http_lua_socket_read_error_retval_handler calls ngx_http_lua_socket_tcp_finalize_read_part directly when the receiveuntil iterator's recv returns an error (e.g. client RST). finalize_read_part clears u->buf_in / u->bufs_in and memzeros u->buffer, but does not detach the compiled-pattern userdata held in u->input_filter_ctx -- so cp->upstream stays pointing at u and cp->state stays > 0 (the DFA stopped mid-match). Later, when LuaJIT GC sweeps the cp userdata, ngx_http_lua_socket_cleanup_compiled_pattern fires and calls ngx_http_lua_socket_tcp_read_prepare(r, u, NULL). cp->state > 0 forces the recovery branches that read u->buf_in->buf->pos -- a NULL deref. Belt-and-braces fix in two places: - read_prepare: bail when u->buf_in == NULL after the cp->state check, before the recovery branches that would deref it. - finalize_read_part: clear cp->upstream, mirroring the same detach that ngx_http_lua_socket_tcp_finalize already performs. This short-circuits the __gc handler at its existing `if (u != NULL)` guard, so the bad code path is never entered. Reproducer: POST a multipart body that ends mid-boundary, then close the socket with SO_LINGER {1,0} so the server reads RST while the DFA is mid-match. Lua handler does: local sock = ngx.req.socket() local iter = sock:receiveuntil("--" .. BOUNDARY) while true do local d = iter(1) if not d then break end end collectgarbage("collect") With the body shaped as six leading dashes against a 12-dash boundary leader, cp->state lands at 6 with no DFA fallback, and the synchronous collectgarbage runs cp's __gc inside the request -- 100% crash rate. Crash signature: #0 ngx_http_lua_socket_tcp_read_prepare (data=0x0) [inlined memcpy at ngx_http_lua_socket_tcp.c, recovery branch, u->buf_in == NULL] #1 ngx_http_lua_socket_cleanup_compiled_pattern ngx_http_lua_socket_tcp.c #2 lj_BC_FUNCC buildvm_x86.dasc #3 gc_call_finalizer lj_gc.c #4 gc_finalize lj_gc.c #5 gc_onestep lj_gc.c #6 lj_gc_fullgc lj_gc.c #7 lua_gc (what=LUA_GCCOLLECT) lj_api.c #8 lj_cf_collectgarbage lib_base.c #9 lj_BC_FUNCC buildvm_x86.dasc #10 ngx_http_lua_run_thread ngx_http_lua_util.c #11 ngx_http_lua_socket_tcp_resume_helper ngx_http_lua_socket_tcp.c #12 ngx_http_lua_socket_tcp_read ngx_http_lua_socket_tcp.c #13 ngx_http_request_handler src/http/ngx_http_request.c
1 parent 41ed26b commit 6335e76

1 file changed

Lines changed: 15 additions & 0 deletions

File tree

src/ngx_http_lua_socket_tcp.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,6 +2803,13 @@ ngx_http_lua_socket_tcp_read_prepare(ngx_http_request_t *r,
28032803
return;
28042804
}
28052805

2806+
/* finalize_read_part may have cleared u->buf_in before the
2807+
* compiled-pattern userdata's __gc runs; the recovery branches
2808+
* below would NULL-deref u->buf_in->buf->pos. */
2809+
if (u->buf_in == NULL) {
2810+
return;
2811+
}
2812+
28062813
b = &u->buffer;
28072814

28082815
if (b->pos - b->start >= cp->state) {
@@ -4203,6 +4210,14 @@ ngx_http_lua_socket_tcp_finalize_read_part(ngx_http_request_t *r,
42034210
ngx_memzero(&u->buffer, sizeof(ngx_buf_t));
42044211
}
42054212

4213+
/* Mirror ngx_http_lua_socket_tcp_finalize: detach the compiled-pattern
4214+
* userdata so a later __gc on it won't re-enter read_prepare on a
4215+
* half-finalised upstream. */
4216+
if (u->input_filter_ctx != NULL && u->input_filter_ctx != u) {
4217+
((ngx_http_lua_socket_compiled_pattern_t *)
4218+
u->input_filter_ctx)->upstream = NULL;
4219+
}
4220+
42064221
if (u->raw_downstream || u->body_downstream) {
42074222
if (r->connection->read->timer_set) {
42084223
ngx_del_timer(r->connection->read);

0 commit comments

Comments
 (0)