From 514b47cad33b8f2e5b74b52d7ff325a1acf9d581 Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Sat, 25 Apr 2026 10:38:02 +0800 Subject: [PATCH] ZNCString: guard Replace/Split against empty-width arguments CString::Replace with an empty sReplace underflowed 'p += uReplaceWidth - 1' to SIZE_MAX and then the per-iteration 'p++' brought p back to the same byte, so the function looped forever appending sWith to the output and eventually OOMed. CString::Split with empty sDelim and bAllowEmpty=false spun in the prefix-skip loops because strncasecmp(p, "", 0) is unconditionally 0 and p += 0 never advances. No in-tree caller currently passes the bad argument, but the public library API should not be a bear trap for module authors. Same shape as #1994. --- src/ZNCString.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ZNCString.cpp b/src/ZNCString.cpp index abda9e83..2a720f02 100644 --- a/src/ZNCString.cpp +++ b/src/ZNCString.cpp @@ -626,6 +626,14 @@ unsigned int CString::Replace(const CString& sReplace, const CString& sWith, unsigned int CString::Replace(CString& sStr, const CString& sReplace, const CString& sWith, const CString& sLeft, const CString& sRight, bool bRemoveDelims) { + // An empty needle would make strncmp(_, _, 0) match at every position + // and `p += uReplaceWidth - 1` underflow to SIZE_MAX, producing an + // infinite loop that appends sWith until OOM. Guard at the entry so + // the invariant "the loop always advances" holds. + if (sReplace.empty()) { + return 0; + } + unsigned int uRet = 0; CString sCopy = sStr; sStr.clear(); @@ -851,7 +859,10 @@ CString::size_type CString::Split(const CString& sDelim, VCString& vsRet, size_type uRightLen = sRight.length(); const char* p = c_str(); - if (!bAllowEmpty) { + // An empty delimiter with bAllowEmpty=false would spin forever in the + // prefix-skip / post-token loops below because `strncasecmp(_, _, 0)` + // returns 0 and `p += 0` never advances. + if (!bAllowEmpty && uDelimLen) { while (strncasecmp(p, sDelim.c_str(), uDelimLen) == 0) { p += uDelimLen; } @@ -890,7 +901,8 @@ CString::size_type CString::Split(const CString& sDelim, VCString& vsRet, sTmp.clear(); p += uDelimLen; - if (!bAllowEmpty) { + // Same zero-width guard as at the top of the function. + if (!bAllowEmpty && uDelimLen) { while (strncasecmp(p, sDelim.c_str(), uDelimLen) == 0) { p += uDelimLen; }