From f03e1609ff438e6455bcd64de14dd4a447e8e6e5 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 30 Jun 2026 18:08:23 +0300 Subject: Reclaim oversized-request fds (inline delete) + fix context ctor UAF Root cause of the earlier 2s0 oversized-delete crashes: the context constructor initialized p_user=NULL but NOT p_map_params or p_response, so ~context's 'if (p_map_params) delete p_map_params' / 'if (p_response) delete p_response' ran on garbage (non-NULL) pointers on the early-delete paths (EOF-empty, hard read error, oversized) -> a corrupted std::map destructor -> infinite _M_erase recursion -> SIGSEGV. It was never a libevent issue. Fix: - src/sock/context.cpp: initialize p_map_params = NULL and p_response = NULL in the constructor (p_user was already NULL). ~context null-guards them, so all early-delete paths are now safe. - src/sock/sock.cpp: the oversized-request branches (headers-incomplete- full and body-incomplete-full) now drop the request inline via del_event()+delete (exactly like the EOF/hard-error sibling branches) instead of leaking the fd/context. This closes the 2s0 fd-exhaustion DoS (the deliberate leak tradeoff) with no crash. Verified: 30 oversized POSTs -> open fd count stays at baseline (7->7, immediate reclaim, no leak), 0 restarts; normal login + multi-user chat work. Independent review: APPROVE (preferred the simplification to inline-delete, applied). --- ychat/src/sock/context.cpp | 2 ++ ychat/src/sock/sock.cpp | 27 +++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ychat/src/sock/context.cpp b/ychat/src/sock/context.cpp index 0e57fed..11af016 100644 --- a/ychat/src/sock/context.cpp +++ b/ychat/src/sock/context.cpp @@ -37,6 +37,8 @@ context::context(sock *p_sock, struct event *p_event, int i_fd) this->i_fd = i_fd; this->i_buf_len = 0; this->c_buf[0] = '\0'; + this->p_map_params = NULL; + this->p_response = NULL; this->p_user = NULL; } diff --git a/ychat/src/sock/sock.cpp b/ychat/src/sock/sock.cpp index 9202902..bf733e7 100644 --- a/ychat/src/sock/sock.cpp +++ b/ychat/src/sock/sock.cpp @@ -35,6 +35,7 @@ #include "sock.h" #include "../tool/tool.h" +#include "context.h" using namespace std; @@ -311,13 +312,17 @@ sock::handle_client_read(int i_fd, short event, void *p_arg) if (i_hdr_end == string::npos) { - // Headers not fully received yet. Guard against oversized header - // floods: if the buffer is already full with no header terminator, the - // request can never be completed - drop it (no re-arm) rather than spin. - // The fd leaks for that one abusive request (see the body branch below); - // preferable to a crash/spin. + // Headers not fully received yet. If the buffer is already full with + // no header terminator (oversized header flood) the request can never + // complete: drop it now (same pattern as the EOF/error branches) rather + // than spin. Otherwise wait for the rest. if ( p_context->i_buf_len < READSOCK ) event_add(p_context->p_event, NULL); + else + { + p_context->del_event(); + delete p_context; + } return; } @@ -332,13 +337,15 @@ sock::handle_client_read(int i_fd, short event, void *p_arg) if (i_have < i_content_len) { // Body still incomplete. If the buffer is already full the body - // can never fit (oversized POST): drop the request without re-arming - // (re-arming a full buffer would spin, and deleting the context - // inside this read callback corrupts libevent). The fd leaks for - // that one abusive request — acceptable for a toy chat and far - // better than a crash/spin. Otherwise wait for the rest. + // can never fit (oversized POST): drop it now (same pattern as the + // EOF/error branches) rather than spin. Otherwise wait for the rest. if ( p_context->i_buf_len < READSOCK ) event_add(p_context->p_event, NULL); + else + { + p_context->del_event(); + delete p_context; + } return; } } -- cgit v1.2.3