summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-07 13:08:57 +0300
committerPaul Buetow <paul@buetow.org>2026-07-07 13:08:57 +0300
commita3908e1cb3dfe475547ecfd17a61849fa237ee18 (patch)
tree64d85e4fee4c89bf01cb20f955d0dd4340aa7710
parentb8d28a189424854faea502be529fcfd6d20d1f4f (diff)
ychat: fix crash on malformed GET/POST request line (uncaught exception)
sock::handle_client_read computed `s_buf.find(" HTTP", 0) + 1` directly into an int for both the GET and POST request-line parsers. When a request has no " HTTP" token at all (e.g. a scanner sending a bare "GET" with no path/version and closing the connection), find() returns string::npos and the "+ 1" wraps a 64-bit npos to 0 before the result is ever compared against string::npos, so the intended invalid-request guard never fired. Execution fell through to substr(5, ...), which throws std::out_of_range whenever the received buffer is shorter than 5 bytes -- an uncaught exception that kills the whole process (seen live on f3s: a vulnerability-scanner probe crashed the pod twice). Fixed by checking find()'s result for npos, and requiring at least 5 bytes to extract from, before doing any arithmetic on it -- both cases now hit the existing "invalid request" (HTTPERR) path instead. Reproduced against the previously deployed image (b8d28a1): a raw 3-byte "GET" with an immediate connection close crashed it every time. The fixed build survives that plus a batch of other short/malformed request lines (bare "GET"/"POST" variants, truncated methods, empty requests), while register/login/wrong-password behavior is unchanged.
-rw-r--r--ychat/src/sock/sock.cpp23
1 files changed, 18 insertions, 5 deletions
diff --git a/ychat/src/sock/sock.cpp b/ychat/src/sock/sock.cpp
index bf733e7..7ce9993 100644
--- a/ychat/src/sock/sock.cpp
+++ b/ychat/src/sock/sock.cpp
@@ -361,30 +361,43 @@ sock::handle_client_read(int i_fd, short event, void *p_arg)
{
b_is_post_request = true;;
- int i_pos = s_buf.find(" HTTP", 0) + 1;
-
- if (i_pos == string::npos && i_pos <= 5)
+ // Crash fix: this used to compute `s_buf.find(" HTTP", 0) + 1` straight
+ // into an int. When " HTTP" isn't present (e.g. a scanner sending a bare
+ // "GET\r\n\r\n" with no path/version), find() returns string::npos and the
+ // "+ 1" wraps a 64-bit npos to 0 before it's ever compared, so the
+ // intended "== string::npos" guard below never fires. Execution then
+ // fell through to substr(5, ...), which throws std::out_of_range (an
+ // uncaught exception that kills the process) whenever the buffer is
+ // shorter than 5 bytes. Checking find()'s result before adding 1 -- and
+ // requiring at least 5 bytes to extract from -- catches both the missing
+ // " HTTP" and the too-short-buffer case as an ordinary invalid request.
+ size_t i_http_pos = s_buf.find(" HTTP", 0);
+
+ if (i_http_pos == string::npos || s_buf.size() < 5)
{
wrap::system_message(HTTPERR);
delete p_context;
return;
}
+ int i_pos = (int) i_http_pos + 1;
s_query.append(s_buf.substr(5, i_pos - 5));
}
else if (strncmp("GET", p_context->c_buf, 3) == 0)
{
b_is_post_request = false;
- int i_pos = s_buf.find(" HTTP", 0) + 1;
+ // Same npos-overflow/short-buffer crash fix as the POST branch above.
+ size_t i_http_pos = s_buf.find(" HTTP", 0);
- if (i_pos == string::npos && i_pos <= 5)
+ if (i_http_pos == string::npos || s_buf.size() < 5)
{
wrap::system_message(HTTPERR);
delete p_context;
return;
}
+ int i_pos = (int) i_http_pos + 1;
s_query.append(s_buf.substr(5, i_pos - 5));
}