Skip to content

Commit 20acb75

Browse files
authored
bugfix: heap-use-after-free in tcp_finalize after receiveuntil iterator GC.
finalize_read_part() only cleared cp->upstream and left u->input_filter_ctx pointing to the compiled pattern, which the Lua GC could free before the socket was finalized. Clear both sides. See #2517.
1 parent c299fcf commit 20acb75

2 files changed

Lines changed: 77 additions & 1 deletion

File tree

‎src/ngx_http_lua_socket_tcp.c‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4211,10 +4211,15 @@ ngx_http_lua_socket_tcp_finalize_read_part(ngx_http_request_t *r,
42114211
ngx_memzero(&u->buffer, sizeof(ngx_buf_t));
42124212
}
42134213

4214-
/* mirror tcp_finalize: detach cp so its __gc is safe */
4214+
/*
4215+
* mirror tcp_finalize: detach cp so its __gc is safe, and drop our
4216+
* reference to cp as well, since cp may be freed by the Lua GC before
4217+
* the socket is finalized.
4218+
*/
42154219
if (u->input_filter_ctx != NULL && u->input_filter_ctx != u) {
42164220
((ngx_http_lua_socket_compiled_pattern_t *)
42174221
u->input_filter_ctx)->upstream = NULL;
4222+
u->input_filter_ctx = NULL;
42184223
}
42194224

42204225
if (u->raw_downstream || u->body_downstream) {

‎t/066-socket-receiveuntil.t‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,3 +2012,74 @@ close: 1 nil
20122012
--- no_error_log
20132013
[error]
20142014
--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}
2015+
2016+
2017+
2018+
=== TEST 27: close the socket after the iterator got a read error and was GC'ed (GH #2517)
2019+
--- config
2020+
server_tokens off;
2021+
location /t {
2022+
content_by_lua_block {
2023+
-- JIT traces may keep the iterator closure alive
2024+
jit.off()
2025+
jit.flush()
2026+
2027+
local sock = ngx.socket.tcp()
2028+
local port = ngx.var.server_port
2029+
2030+
local ok, err = sock:connect("127.0.0.1", port)
2031+
if not ok then
2032+
ngx.say("failed to connect: ", err)
2033+
return
2034+
end
2035+
2036+
local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
2037+
2038+
local bytes, err = sock:send(req)
2039+
if not bytes then
2040+
ngx.say("failed to send request: ", err)
2041+
return
2042+
end
2043+
2044+
local weak = setmetatable({}, { __mode = "v" })
2045+
2046+
local function read_until_error()
2047+
local reader = sock:receiveuntil("--no-such-boundary")
2048+
weak[1] = reader
2049+
2050+
while true do
2051+
local data, err = reader(1)
2052+
if not data then
2053+
ngx.say("failed to read: ", err)
2054+
return
2055+
end
2056+
end
2057+
end
2058+
2059+
read_until_error()
2060+
2061+
for _ = 1, 4 do
2062+
collectgarbage("collect")
2063+
end
2064+
2065+
ngx.say("reader collected: ", weak[1] == nil)
2066+
2067+
ok, err = sock:close()
2068+
ngx.say("close: ", ok, " ", err)
2069+
}
2070+
}
2071+
2072+
location /foo {
2073+
content_by_lua_block {
2074+
ngx.print("hello world")
2075+
}
2076+
}
2077+
--- request
2078+
GET /t
2079+
--- response_body
2080+
failed to read: closed
2081+
reader collected: true
2082+
close: 1 nil
2083+
--- no_error_log
2084+
[error]
2085+
--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}

0 commit comments

Comments
 (0)