From acdd9e754a536d542099951e4c9a6acc8cdf1c51 Mon Sep 17 00:00:00 2001 From: frosty Date: Sun, 16 Aug 2026 17:55:19 -0400 Subject: fix: harden HTTP request and response parsing --- src/server.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 28 deletions(-) (limited to 'src/server.c') diff --git a/src/server.c b/src/server.c index 7d5eb8f..33fa54a 100644 --- a/src/server.c +++ b/src/server.c @@ -21,6 +21,8 @@ static volatile sig_atomic_t g_shutdown_requested = 0; +static bool validate_request_headers(const char *request_buffer); + static void signal_handler(int sig) { (void)sig; g_shutdown_requested = 1; @@ -280,6 +282,14 @@ void handle_client_connection(int new_socket) { &started_at); return; } + if (!validate_request_headers(buffer)) { + beaker_log("SECURITY", + "handle_client_connection: Rejected malformed headers."); + send_status("400 Bad Request"); + finish_client_connection(new_socket, requested_path, method, log_path, + &started_at); + return; + } size_t request_line_len = first_line_end - buffer; if (request_line_len >= sizeof(request_line)) { beaker_log("ERROR", "handle_client_connection: Request line too long.\n"); @@ -454,42 +464,112 @@ const char *beaker_get_remote_addr(void) { static __thread char g_header_value[MAX_VALUE_LEN]; +static bool is_valid_request_header_value(const char *start, const char *end) { + for (const char *cursor = start; cursor < end; cursor++) { + unsigned char c = (unsigned char)*cursor; + if ((c < 0x20 && c != '\t') || c == 0x7f) + return false; + } + return true; +} + +static bool validate_request_headers(const char *request_buffer) { + if (request_buffer == NULL) + return false; + + const char *request_line_end = strstr(request_buffer, "\r\n"); + if (request_line_end == NULL) + return false; + + const char *cursor = request_line_end + 2; + int host_count = 0; + while (true) { + const char *line_end = strstr(cursor, "\r\n"); + if (line_end == NULL) + return false; + if (line_end == cursor) + return host_count <= 1; + if (*cursor == ' ' || *cursor == '\t') + return false; + + const char *colon = memchr(cursor, ':', (size_t)(line_end - cursor)); + if (colon == NULL || colon == cursor) + return false; + + size_t name_len = (size_t)(colon - cursor); + if (!beaker_is_valid_http_token_span(cursor, name_len) || + !is_valid_request_header_value(colon + 1, line_end)) { + return false; + } + + if (name_len == strlen("Host") && + strncasecmp(cursor, "Host", name_len) == 0 && ++host_count > 1) { + return false; + } + cursor = line_end + 2; + } +} + const char *beaker_get_header(const char *name) { - if (name == NULL) - return ""; - - size_t name_len = strlen(name); - if (name_len == 0) - return ""; - - if (strstr(name, "\r\n") != NULL) - return ""; - - char *buffer = current_request_buffer; - char *search_end = strstr(buffer, "\r\n\r\n"); - if (search_end == NULL) - search_end = buffer + strlen(buffer); - while (buffer < search_end) { - if (strncasecmp(buffer, name, name_len) == 0 && buffer[name_len] == ':') { - char *value_start = buffer + name_len + 1; - while (*value_start == ' ') + g_header_value[0] = '\0'; + if (!beaker_is_valid_http_token(name)) + return g_header_value; + + size_t requested_name_len = strlen(name); + const char *request_line_end = strstr(current_request_buffer, "\r\n"); + if (request_line_end == NULL) + return g_header_value; + + const char *cursor = request_line_end + 2; + bool found = false; + while (true) { + const char *line_end = strstr(cursor, "\r\n"); + if (line_end == NULL || line_end == cursor) + break; + if (*cursor == ' ' || *cursor == '\t') + return g_header_value; + + const char *colon = memchr(cursor, ':', (size_t)(line_end - cursor)); + if (colon == NULL || colon == cursor) + return g_header_value; + + size_t header_name_len = (size_t)(colon - cursor); + if (header_name_len == requested_name_len && + strncasecmp(cursor, name, requested_name_len) == 0) { + if (found) { + beaker_log("SECURITY", + "beaker_get_header: Rejected duplicate header value."); + g_header_value[0] = '\0'; + return g_header_value; + } + + const char *value_start = colon + 1; + while (value_start < line_end && + (*value_start == ' ' || *value_start == '\t')) { value_start++; + } + const char *value_end = line_end; + while (value_end > value_start && + (value_end[-1] == ' ' || value_end[-1] == '\t')) { + value_end--; + } - char *value_end = strstr(value_start, "\r\n"); - if (value_end == NULL) - value_end = search_end; - size_t value_len = value_end - value_start; - if (value_len >= sizeof(g_header_value)) { - value_len = sizeof(g_header_value) - 1; + size_t value_len = (size_t)(value_end - value_start); + if (value_len >= sizeof(g_header_value) || + !is_valid_request_header_value(value_start, value_end)) { + beaker_log("SECURITY", + "beaker_get_header: Rejected invalid header value."); + g_header_value[0] = '\0'; + return g_header_value; } - strncpy(g_header_value, value_start, value_len); + memcpy(g_header_value, value_start, value_len); g_header_value[value_len] = '\0'; - return g_header_value; + found = true; } - buffer++; + cursor = line_end + 2; } - return ""; + return g_header_value; } void beaker_set_request_buffer(const char *buffer) { -- cgit v1.3