diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-07 13:08:57 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-07 13:08:57 +0300 |
| commit | a3908e1cb3dfe475547ecfd17a61849fa237ee18 (patch) | |
| tree | 64d85e4fee4c89bf01cb20f955d0dd4340aa7710 | |
| parent | b8d28a189424854faea502be529fcfd6d20d1f4f (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.cpp | 23 |
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)); } |
