Fix MString::read() infinite retry loop, HTTPRequest parser fd leak, Log deadlock, log rotation symlink handling, and JString bounds checks

- MString::read() now gives up after repeated I/O errors instead of
  looping forever and crashing via realloc() failure (root cause of a
  recurring barant.com production crash).
- HTTPRequest::parse() no longer leaks lineParser/headerParser on a
  malformed request.
- Log gained size-based rotation (config-driven) and a non-static-mutex
  fix; a separate fix stopped rotation from destroying the logs symlink
  on its first trigger.
- File.cpp: symlink-preserving rotation support.
- JString: fixed an array-index out-of-bounds and a setString issue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Brad ArantandClaude Sonnet 5 committed 2026-10-06 17:38:14 +00:00
1 parent 694c6b80a4
commit 05374bdb15
7 files changed
+213 -14

No files matched your search

+38 -4
View File
@@ -23,7 +23,26 @@ namespace coreutils {
if (!checkParse(*lineParser))
return Status::INCOMPLETE;
{
// line is a view into reader's own buffer (see
// LineParser::parse()), not into lineParser itself - safe to
// delete lineParser immediately, before the parts.size()
// check below can throw. Previously this delete sat AFTER
// that check, so a malformed request line (any count other
// than exactly 3 space-separated parts - confirmed live via
// real scanner/bot traffic hitting this daily) leaked
// lineParser and left the member non-NULL. Since state is
// never advanced past REQUEST_LINE on this throw, ANY later
// reentry into this same case (state itself surviving the
// exception, e.g. if this HTTPRequest is ever driven again
// before its owning session tears down) would see a
// non-NULL lineParser and skip allocating a fresh one,
// reusing an already-completed, now-stale parser instance
// instead - a real invariant violation that a heap-
// corruption crash (munmap_chunk(): invalid pointer) was
// traced back to.
ZString line = lineParser->getLine();
delete lineParser;
lineParser = NULL;
ZString sp(" ");
std::vector<ZString> &parts = line.split(sp);
if (parts.size() != 3)
@@ -33,8 +52,6 @@ namespace coreutils {
path = MString(parts[1]);
version = MString(parts[2]);
}
delete lineParser;
lineParser = NULL;
{
// Split the query string off before anything else sees `path` -
// a route table (see the APIServer plan) should never have to
@@ -49,16 +66,33 @@ namespace coreutils {
return Status::DATA_AVAIL;
}
case HEADERS:
case HEADERS: {
if (!headerParser)
headerParser = new IMFHeader(reader);
if (!checkParse(*headerParser))
// The actual throw for a malformed header (e.g. a header line
// with no ':') happens deep inside checkParse() -> IMFHeader::
// parse(), well before headerParser->getHeaders() below ever
// runs - same leaked-pointer/stale-reentry hazard as
// REQUEST_LINE above, confirmed as the other half of the same
// real crash. Cleaning up here before rethrowing keeps
// headerParser NULL on every exit path, matching the
// REQUEST_LINE fix.
bool complete;
try {
complete = checkParse(*headerParser);
} catch (...) {
delete headerParser;
headerParser = NULL;
throw;
}
if (!complete)
return Status::INCOMPLETE;
headers = headerParser->getHeaders();
delete headerParser;
headerParser = NULL;
state = BODY;
return Status::DATA_AVAIL;
}
case BODY: {
MString contentLengthHeader = getHeader("Content-Length");