From 03c3fd19453f4a5b55e1de7f9b2817326b7066c8 Mon Sep 17 00:00:00 2001 From: Gabriel Clima Date: Mon, 25 May 2026 07:23:57 +0000 Subject: [PATCH 1/2] bugfix: SIGSEGV in receiveuntil __gc on aborted multipart upload. read_error_retval_handler calls finalize_read_part directly when the receiveuntil iterator's recv errors. That clears u->buf_in but leaves cp->upstream live with cp->state > 0. Later GC fires cleanup_compiled_pattern -> read_prepare, which derefs the now-NULL u->buf_in. Mirror tcp_finalize's cp->upstream = NULL detach so __gc's existing `if (u != NULL)` guard short-circuits. Backtrace: ngx_http_lua_socket_tcp_read_prepare ngx_http_lua_socket_cleanup_compiled_pattern lj_BC_FUNCC gc_call_finalizer gc_finalize gc_onestep lj_gc_fullgc lua_gc lj_cf_collectgarbage lj_BC_FUNCC ngx_http_lua_run_thread ngx_http_lua_socket_tcp_resume_helper ngx_http_lua_access_handler ngx_http_core_access_phase ngx_http_core_run_phases ngx_http_lua_socket_tcp_read ngx_http_request_handler ngx_epoll_process_events ngx_process_events_and_timers ngx_worker_process_cycle ngx_spawn_process ngx_start_worker_processes ngx_master_process_cycle main --- src/ngx_http_lua_socket_tcp.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c index ee4befa90b..027d534a5e 100644 --- a/src/ngx_http_lua_socket_tcp.c +++ b/src/ngx_http_lua_socket_tcp.c @@ -4203,6 +4203,12 @@ ngx_http_lua_socket_tcp_finalize_read_part(ngx_http_request_t *r, ngx_memzero(&u->buffer, sizeof(ngx_buf_t)); } + /* mirror tcp_finalize: detach cp so its __gc is safe */ + if (u->input_filter_ctx != NULL && u->input_filter_ctx != u) { + ((ngx_http_lua_socket_compiled_pattern_t *) + u->input_filter_ctx)->upstream = NULL; + } + if (u->raw_downstream || u->body_downstream) { if (r->connection->read->timer_set) { ngx_del_timer(r->connection->read); From 073a37277e958c8f3463a9afe600e9070156cd14 Mon Sep 17 00:00:00 2001 From: lijunlong Date: Mon, 25 May 2026 19:23:08 +0800 Subject: [PATCH 2/2] add tests. --- t/067-req-socket.t | 67 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/t/067-req-socket.t b/t/067-req-socket.t index ae6bc3f3b6..8614d07d44 100644 --- a/t/067-req-socket.t +++ b/t/067-req-socket.t @@ -14,7 +14,7 @@ use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); repeat_each(2); -plan tests => repeat_each() * (blocks() * 3 + 9); +plan tests => repeat_each() * (blocks() * 3 + 11); our $HtmlDir = html_dir; @@ -1206,3 +1206,68 @@ qr/\Agot the request socket \z/ms --- no_error_log [error] + + + +=== TEST 20: receiveuntil compiled-pattern GC after aborted multipart body (GH #2503) +--- config + location = /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", ngx.var.server_port) + if not ok then + ngx.say("connect failed: ", err) + return + end + local req = "POST /upload HTTP/1.1\r\n" + .. "Host: 127.0.0.1\r\n" + .. "Content-Type: multipart/form-data; " + .. "boundary=----------GFioQpMK0vv2\r\n" + .. "Content-Length: 1048576\r\n" + .. "Connection: close\r\n\r\n" + .. "------" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("send failed: ", err) + return + end + ngx.sleep(0.2) + sock:close() + ngx.sleep(0.5) + ngx.say("ok") + } + } + + location = /upload { + content_by_lua_block { + local BOUNDARY = "----------GFioQpMK0vv2" + local sock, err = ngx.req.socket() + if not sock then + ngx.log(ngx.ERR, "no req socket: ", err) + return + end + sock:settimeout(2000) + local iter = sock:receiveuntil("--" .. BOUNDARY) + while true do + local data, e = iter(1) + if not data then + ngx.log(ngx.WARN, "iter err: ", e) + break + end + end + iter = nil + collectgarbage("collect") + collectgarbage("collect") + ngx.log(ngx.WARN, "receiveuntil gc done, no crash") + } + } +--- request +GET /t +--- response_body +ok +--- error_log +receiveuntil gc done, no crash +--- no_error_log +[alert] +[emerg] +--- skip_eval: 5:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} ne "")