From 3cedf31d4c7deece664d5afd0960089b6fb5f2ca Mon Sep 17 00:00:00 2001 From: yhirose Date: Mon, 13 Apr 2026 23:19:31 -0400 Subject: [PATCH] Fix #2427 (#2428) * Fix #2427 * Use setarch -R on Linux to fix ASAN crash on WSL2 WSL2 uses high-entropy ASLR which conflicts with ASAN's shadow memory requirements, causing the ASAN runtime to crash at startup. Running tests via setarch -R (ADDR_NO_RANDOMIZE) disables ASLR for the test process, allowing ASAN to initialize correctly. --- test/Makefile | 6 ++++-- test/test.cc | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/test/Makefile b/test/Makefile index 40b2088..eb32bf1 100644 --- a/test/Makefile +++ b/test/Makefile @@ -18,6 +18,8 @@ ifneq ($(OS), Windows_NT) OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -lwolfssl + # Disable ASLR for ASAN compatibility on WSL2 (high-entropy ASLR conflicts with ASAN shadow memory) + SETARCH = setarch $(shell uname -m) -R endif endif @@ -59,7 +61,7 @@ STYLE_CHECK_FILES = $(filter-out httplib.h httplib.cc, \ $(wildcard example/*.h example/*.cc fuzzing/*.h fuzzing/*.cc *.h *.cc ../httplib.h)) all : test test_split - LSAN_OPTIONS=suppressions=lsan_suppressions.txt ./test + LSAN_OPTIONS=suppressions=lsan_suppressions.txt $(SETARCH) ./test SHARDS ?= 4 @@ -69,7 +71,7 @@ define run_parallel for i in $$(seq 0 $$(($(SHARDS) - 1))); do \ GTEST_TOTAL_SHARDS=$(SHARDS) GTEST_SHARD_INDEX=$$i \ LSAN_OPTIONS=suppressions=lsan_suppressions.txt \ - ./$(1) --gtest_color=yes > $(1)_shard_$$i.log 2>&1 & \ + $(SETARCH) ./$(1) --gtest_color=yes > $(1)_shard_$$i.log 2>&1 & \ done; \ wait; \ for i in $$(seq 0 $$(($(SHARDS) - 1))); do \ diff --git a/test/test.cc b/test/test.cc index a84750a..30daf37 100644 --- a/test/test.cc +++ b/test/test.cc @@ -14275,8 +14275,23 @@ static std::thread serve_single_response(std::promise &port_promise, auto cli = ::accept(srv, reinterpret_cast(&cli_addr), &cli_len); if (cli != INVALID_SOCKET) { - char buf[4096]; - ::recv(cli, buf, sizeof(buf), 0); + // Read the complete HTTP request (until the blank line that terminates + // headers) before sending the response. If the server closes the socket + // while the client's request data is still unread in the kernel receive + // buffer, the TCP stack sends RST instead of FIN. That RST resets the + // connection on both sides: it discards the client's receive buffer + // (which already holds our response) and causes any in-progress write on + // the client to fail with ECONNRESET. Reading all request data first + // ensures close() emits a graceful FIN. + { + char rbuf[256]; + std::string req; + while (req.find("\r\n\r\n") == std::string::npos) { + auto n = ::recv(cli, rbuf, sizeof(rbuf), 0); + if (n <= 0) { break; } + req.append(rbuf, static_cast(n)); + } + } ::send(cli, #ifdef _WIN32