Skip to content

Harden WebSocket request parsing for header casing, body bounds, cookie conversion, and MinGW portability - #27

Merged
mrjimenez merged 6 commits into
masterfrom
copilot/fix-http-header-comparison
Jun 9, 2026
Merged

Harden WebSocket request parsing for header casing, body bounds, cookie conversion, and MinGW portability#27
mrjimenez merged 6 commits into
masterfrom
copilot/fix-http-header-comparison

Conversation

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown

Summary

This PR hardens CWebSocket HTTP parsing in three places: case-insensitive Content-Length handling, safe POST body boundary checks without signed/unsigned pitfalls, and strict strtoull cookie parsing validation to reject malformed session IDs.

  • Header name matching

    • Use case-insensitive lookup for Content-Length so mixed-case headers are handled correctly.
    • Implement this via a portable local helper (FindHeaderCaseInsensitive) using strncasecmp, instead of strcasestr, to keep MinGW builds green.
  • POST body length/bounds validation

    • Reject non-positive content lengths.
    • Guard missing body delimiter.
    • Replace signed/unsigned pointer-length comparison with overflow-safe unsigned bounds checks before dispatching the request body.
  • Session cookie parsing hardening

    • Validate strtoull conversion using errno + endptr.
    • Accept only properly terminated numeric cookie values before setting sessid.
char *cont_len = FindHeaderCaseInsensitive(m_pBuf, "Content-Length");

uint32 bodyOffset = static_cast<uint32>(cont - m_pBuf);
uint32 bodyLen = static_cast<uint32>(len);
if (bodyLen <= m_dwRecv && bodyOffset <= m_dwRecv - bodyLen) {
	OnRequestReceived(m_pBuf, cont, len);
}

Test plan

  • Verified CI failure root cause from the failing MinGW jobs (strcasestr unavailable on that toolchain) and updated the implementation to a portable case-insensitive header search.
  • Confirmed the targeted file no longer uses strcasestr.
  • Ran secret scanning on modified files (no secrets detected).
  • CodeQL check was invoked, but the tool timed out in this environment.
Original prompt
Please apply the following diffs and create a pull request.
Once the PR is ready, give it a title based on the messages of the fixes being applied.

[{"message":"The HTTP header field name comparison is case-sensitive, but HTTP headers are case-insensitive per RFC 7230. The code should use a case-insensitive comparison (like `strcasestr` if available) or normalize the header before searching to properly handle \"content-length\", \"Content-length\", etc.","fixFiles":[{"filePath":"src/webserver/src/WebSocket.cpp","diff":"diff --git a/src/webserver/src/WebSocket.cpp b/src/webserver/src/WebSocket.cpp\n--- a/src/webserver/src/WebSocket.cpp\n+++ b/src/webserver/src/WebSocket.cpp\n@@ -121,7 +121,7 @@\n \t//\n \t// \"POST\" have \"Content-Length\"\n \tif ( m_IsPost ) {\n-\t\tchar *cont_len = strstr(m_pBuf, \"Content-Length\");\n+\t\tchar *cont_len = strcasestr(m_pBuf, \"Content-Length\");\n \t\t// do we have received all the line ?\n \t\tif ( cont_len && strstr(cont_len, \"\\r\\n\\r\\n\") ) {\n \t\t\tcont_len += strlen(\"Content-Length:\");\n"}]},{"message":"There is a signed/unsigned comparison issue here. The left side of the comparison involves pointer arithmetic that could potentially be negative, while `m_dwRecv` is unsigned. This should be refactored to avoid the cast and potential overflow issues. Consider: `if ( (uint32)(cont - m_pBuf) + len <= m_dwRecv )` with proper overflow checking.","fixFiles":[{"filePath":"src/webserver/src/WebSocket.cpp","diff":"diff --git a/src/webserver/src/WebSocket.cpp b/src/webserver/src/WebSocket.cpp\n--- a/src/webserver/src/WebSocket.cpp\n+++ b/src/webserver/src/WebSocket.cpp\n@@ -128,14 +128,24 @@\n \t\t\t// can be white space following\n \t\t\twhile ( isspace(*cont_len) ) cont_len++;\n \t\t\tint len = atoi(cont_len);\n-\t\t\tif ( !len ) {\n+\t\t\tif ( len <= 0 ) {\n \t\t\t\tClose();\n \t\t\t\treturn ;\n \t\t\t}\n \t\t\t// do we have all of data ?\n \t\t\tchar *cont = strstr(m_pBuf, \"\\r\\n\\r\\n\");\n+\t\t\tif ( !cont ) {\n+\t\t\t\tClose();\n+\t\t\t\treturn ;\n+\t\t\t}\n \t\t\tcont += 4;\n-\t\t\tif ( cont - m_pBuf + len <= (int)m_dwRecv ) {\n+\t\t\tif ( cont < m_pBuf ) {\n+\t\t\t\tClose();\n+\t\t\t\treturn ;\n+\t\t\t}\n+\t\t\tuint32 bodyOffset = static_cast<uint32>(cont - m_pBuf);\n+\t\t\tuint32 bodyLen = static_cast<uint32>(len);\n+\t\t\tif ( bodyLen <= m_dwRecv && bodyOffset <= m_dwRecv - bodyLen ) {\n \t\t\t\tOnRequestReceived(m_pBuf, cont, len);\n \t\t\t}\n \t\t}\n"}]},{"message":"The `strtoull` function is called without checking for conversion errors. If the cookie value contains invalid characters, `strtoull` may return 0 or ULLONG_MAX and set errno, but these conditions are not checked. This could lead to session ID confusion if malformed cookie values are sent.","fixFiles":[{"filePath":"src/webserver/src/WebSocket.cpp","diff":"diff --git a/src/webserver/src/WebSocket.cpp b/src/webserver/src/WebSocket.cpp\n--- a/src/webserver/src/WebSocket.cpp\n+++ b/src/webserver/src/WebSocket.cpp\n@@ -26,6 +26,8 @@\n \n \n #include \"WebSocket.h\"\n+#include <cerrno>\n+#include <cctype>\n \n \n CWebSocket::CWebSocket(CWebServerBase *parent)\n@@ -228,7 +230,15 @@\n \t\tif ( current_cookie ) {\n \t\t\tchar *value = strchr(current_cookie, '=');\n \t\t\tif ( value ) {\n-\t\t\t\tsessid = strtoull(++value, NULL, 10);\n+\t\t\t\t++value;\n+\t\t\t\terrno = 0;\n+\t\t\t\tchar *endptr = NULL;\n+\t\t\t\tunsigned long long parsed = strtoull(value, &endptr, 10);\n+\t\t\t\tif ( endptr != value && errno != ERANGE &&\n+\t\t\t\t\t(*endptr == '\\0' || *endptr == ';' || *endptr == '\\r' || *endptr == '\\n' ||\n+\t\t\t\t\t std::isspace(static_cast<unsigned char>(*endptr))) ) {\n+\t\t\t\t\tsessid = static_cast<uint64_t>(parsed);\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \t}\n"}]}]

Copilot AI changed the title [WIP] Fix case-sensitive comparison for HTTP header fields Harden WebSocket request parsing for header casing, body bounds, and cookie conversion Jun 9, 2026
Copilot AI requested a review from mrjimenez June 9, 2026 00:46
@mrjimenez

Copy link
Copy Markdown

@copilot CI is red for mingw, fix it please.

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Author

@copilot CI is red for mingw, fix it please.

Fixed in commit 7af3924. I replaced the strcasestr call (not available on mingw) with a local case-insensitive header search helper so mingw builds can compile this code path.

Copilot AI changed the title Harden WebSocket request parsing for header casing, body bounds, and cookie conversion Harden WebSocket request parsing for header casing, body bounds, cookie conversion, and MinGW portability Jun 9, 2026
mrjimenez added 3 commits June 8, 2026 22:20
The outer guard `strstr(cont_len, "\r\n\r\n")` already proves the
sequence is present in the buffer. Since cont_len is a pointer into
m_pBuf, strstr(m_pBuf, "\r\n\r\n") is guaranteed to succeed, so the
subsequent NULL check is unreachable.
strstr returns NULL or a pointer within its haystack; it can never
return a value less than the haystack base. The cont < m_pBuf branch
is unreachable on any conforming implementation.
std::isspace already returns true for '\r' and '\n', making the two
explicit comparisons in the cookie endptr validation redundant.
@mrjimenez
mrjimenez marked this pull request as ready for review June 9, 2026 01:29
@mrjimenez
mrjimenez merged commit 20d32da into master Jun 9, 2026
20 checks passed
@mrjimenez
mrjimenez deleted the copilot/fix-http-header-comparison branch June 9, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants