Files
FTL/patch/civetweb/0001-add-pihole-mods.patch
Dominik 6fc8deed01 Fix thread-safety issues causing SIGSEGV under concurrent API load
pi_hole_extra_headers is a global char[1024] buffer written by API
handlers and read/cleared by civetweb's send_additional_header(), but
civetweb runs up to 50 worker threads concurrently. When multiple
threads handle authenticated requests in parallel, one thread can
overwrite or clear another's header data, causing wrong Set-Cookie
headers to be sent to wrong clients or cookies to be dropped entirely.

Make pi_hole_extra_headers _Thread_local so each worker thread gets its
own buffer. This is safe because civetweb handles each request entirely
within a single thread.

The auth_data session array has a similar race: concurrent threads
read/modify sessions without synchronization. Add a pthread mutex
protecting all auth_data access, using AUTOLOCK/AUTOUNLOCK macros based
on __attribute__((cleanup)) for RAII-style auto-unlock — ensuring the
mutex is released on every exit path, including hidden returns inside
JSON macros that do `return 500` on allocation failure.

Change api->session from a pointer into auth_data to an embedded struct
copy so downstream API handlers read from a per-request snapshot rather
than shared state. Use JSON_COPY_STR_TO_OBJECT for auth_data strings
so the JSON tree owns its own copies after the lock is released.

Fixes: #2824

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-04-05 20:02:16 +02:00

90 lines
3.3 KiB
Diff

diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c
index 81f642be..ed360a76 100644
--- a/src/webserver/civetweb/civetweb.c
+++ b/src/webserver/civetweb/civetweb.c
@@ -4135,6 +4135,14 @@ send_additional_header(struct mg_connection *conn)
if (header && header[0]) {
mg_response_header_add_lines(conn, header);
}
+
+ /*************** Pi-hole modification ****************/
+ if (pi_hole_extra_headers[0] != '\0') {
+ mg_response_header_add_lines(conn, pi_hole_extra_headers);
+ // Invalidate extra headers after having sent them to avoid repetitions
+ pi_hole_extra_headers[0] = '\0';
+ }
+ /*****************************************************/
}
@@ -4530,6 +4538,48 @@ mg_send_http_error_impl(struct mg_connection *conn,
}
+/************************************** Pi-hole method **************************************/
+CIVETWEB_API int
+my_send_http_error_headers(struct mg_connection *conn,
+ int status, const char* mime_type,
+ long long content_length)
+{
+ if ((mime_type == NULL) || (*mime_type == 0)) {
+ /* No content type defined: default to text/html */
+ mime_type = "text/html";
+ }
+
+ mg_response_header_start(conn, status);
+ send_no_cache_header(conn);
+ send_additional_header(conn);
+ mg_response_header_add(conn, "Content-Type", mime_type, -1);
+ if (content_length < 0) {
+ /* Size not known. Use chunked encoding (HTTP/1.x) */
+ if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) {
+ /* Only HTTP/1.x defines "chunked" encoding, HTTP/2 does not*/
+ mg_response_header_add(conn, "Transfer-Encoding", "chunked", -1);
+ }
+ } else {
+ char len[32];
+ int trunc = 0;
+ mg_snprintf(conn,
+ &trunc,
+ len,
+ sizeof(len),
+ "%" UINT64_FMT,
+ (uint64_t)content_length);
+ if (!trunc) {
+ /* Since 32 bytes is enough to hold any 64 bit decimal number,
+ * !trunc is always true */
+ mg_response_header_add(conn, "Content-Length", len, -1);
+ }
+ }
+ mg_response_header_send(conn);
+
+ return 0;
+}
+/********************************************************************************************/
+
CIVETWEB_API int
mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...)
{
diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h
index 7ea45fb2..f879ff3e 100644
--- a/src/webserver/civetweb/civetweb.h
+++ b/src/webserver/civetweb/civetweb.h
@@ -963,6 +964,16 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn,
PRINTF_FORMAT_STRING(const char *fmt),
...) PRINTF_ARGS(3, 4);
+/************************************** Pi-hole method **************************************/
+int my_send_http_error_headers(struct mg_connection *conn,
+ int status, const char* mime_type,
+ long long content_length);
+
+// Buffer used for additional "Set-Cookie" headers
+#define PIHOLE_HEADERS_MAXLEN 1024
+extern _Thread_local char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN];
+/********************************************************************************************/
+
/* Send "HTTP 200 OK" response header.
* After calling this function, use mg_write or mg_send_chunk to send the