CString::Replace and CString::Split loop forever on empty separator
Two sibling library-API bugs in CString. Same shape as #1994: a missing
input guard turns a degenerate argument into UB / unbounded loop. No
in-tree caller passes the bad argument today, but the public API is exposed
to module authors and to anyone embedding libznc.
// src/ZNCString.cpp
626:unsignedintCString::Replace(CString&sStr,constCString&sReplace,627:constCString&sWith,constCString&sLeft,628:constCString&sRight,boolbRemoveDelims){...656:}elseif(!bInside&&657:strncmp(p,sReplace.c_str(),uReplaceWidth)==0){658:sStr+=sWith;659:p+=uReplaceWidth-1;// <-- when uReplaceWidth==0: p -= 1
660:uRet++;...665:p++;// <-- back to where we started
When sReplace.empty(), strncmp(_, _, 0) == 0 always matches, the
arithmetic at 659 underflows size_t to SIZE_MAX, the p++ at 665
returns to the same byte, and the loop appends sWith indefinitely until
ASan kills the process for OOM.
// src/ZNCString.cpp
854:if(!bAllowEmpty){855:while(strncasecmp(p,sDelim.c_str(),uDelimLen)==0){856:p+=uDelimLen;// <-- 0 when sDelim.empty()
857:}858:}...893:if(!bAllowEmpty){894:while(strncasecmp(p,sDelim.c_str(),uDelimLen)==0){895:p+=uDelimLen;// <-- same bug
896:}897:}
strncasecmp(_, _, 0) is unconditionally 0; p never advances; the loop
spins on the CPU forever. No allocation, so this hangs rather than OOMs.
PoC:
VCStringout;CStrings="abc";s.Split(/*sDelim=*/"",out,/*bAllowEmpty=*/false);// never returns
Reachability
I grepped every caller in src/ and modules/ and didn't find one that
passes an empty sReplace or empty sDelim. So this is the same
"library-API correctness, not currently reachable" framing as #1994. I'm
just bringing the contracts of these two functions in line with what
callers expect.
Fix
// in CString::Replace
if(sReplace.empty())return0;// in CString::Split, guard the prefix-skip loop
if(!bAllowEmpty&&!sDelim.empty()){while(strncasecmp(p,sDelim.c_str(),uDelimLen)==0)p+=uDelimLen;}// and likewise at line 894.
Or, more aggressively, bail out at the top of Split if sDelim.empty().
Happy to PR with a couple of regression tests in test/StringTest.cpp
mirroring #1995.
# `CString::Replace` and `CString::Split` loop forever on empty separator
Two sibling library-API bugs in `CString`. Same shape as #1994: a missing
input guard turns a degenerate argument into UB / unbounded loop. No
in-tree caller passes the bad argument today, but the public API is exposed
to module authors and to anyone embedding libznc.
## 1) `CString::Replace`: empty `sReplace` causes pointer underflow + OOM
```cpp
// src/ZNCString.cpp
626: unsigned int CString::Replace(CString& sStr, const CString& sReplace,
627: const CString& sWith, const CString& sLeft,
628: const CString& sRight, bool bRemoveDelims) {
...
656: } else if (!bInside &&
657: strncmp(p, sReplace.c_str(), uReplaceWidth) == 0) {
658: sStr += sWith;
659: p += uReplaceWidth - 1; // <-- when uReplaceWidth==0: p -= 1
660: uRet++;
...
665: p++; // <-- back to where we started
```
When `sReplace.empty()`, `strncmp(_, _, 0) == 0` always matches, the
arithmetic at 659 underflows `size_t` to `SIZE_MAX`, the `p++` at 665
returns to the same byte, and the loop appends `sWith` indefinitely until
ASan kills the process for OOM.
PoC (3-second guard prevents the OOM kill):
```cpp
#include <cstdio>
#include <signal.h>
#include <unistd.h>
#include <znc/ZNCString.h>
extern "C" void to(int) { _exit(124); }
int main() {
signal(SIGALRM, to); alarm(3);
CString s = "abc";
CString::Replace(s, /*sReplace=*/"", /*sWith=*/"X");
}
```
```
ZNCString.cpp:659:15: runtime error: addition of unsigned offset to
0x... overflowed to 0x...
[3s later] killed by SIGALRM
```
## 2) `CString::Split`: empty `sDelim` + `bAllowEmpty=false` infinite-loops
Same pattern, two locations:
```cpp
// src/ZNCString.cpp
854: if (!bAllowEmpty) {
855: while (strncasecmp(p, sDelim.c_str(), uDelimLen) == 0) {
856: p += uDelimLen; // <-- 0 when sDelim.empty()
857: }
858: }
...
893: if (!bAllowEmpty) {
894: while (strncasecmp(p, sDelim.c_str(), uDelimLen) == 0) {
895: p += uDelimLen; // <-- same bug
896: }
897: }
```
`strncasecmp(_, _, 0)` is unconditionally 0; `p` never advances; the loop
spins on the CPU forever. No allocation, so this hangs rather than OOMs.
PoC:
```cpp
VCString out;
CString s = "abc";
s.Split(/*sDelim=*/"", out, /*bAllowEmpty=*/false); // never returns
```
## Reachability
I grepped every caller in `src/` and `modules/` and didn't find one that
passes an empty `sReplace` or empty `sDelim`. So this is the same
"library-API correctness, not currently reachable" framing as #1994. I'm
just bringing the contracts of these two functions in line with what
callers expect.
## Fix
```cpp
// in CString::Replace
if (sReplace.empty()) return 0;
// in CString::Split, guard the prefix-skip loop
if (!bAllowEmpty && !sDelim.empty()) {
while (strncasecmp(p, sDelim.c_str(), uDelimLen) == 0) p += uDelimLen;
}
// and likewise at line 894.
```
Or, more aggressively, bail out at the top of `Split` if `sDelim.empty()`.
Happy to PR with a couple of regression tests in `test/StringTest.cpp`
mirroring #1995.
<!-- github-sync-id:4326793889 -->
Why you opening and closing the same issue as your other issues? And if you do, at least a link would be useful with explanation
Same as #2009 ?
Why you opening and closing the same issue as your other issues? And if you do, at least a link would be useful with explanation
<!-- github-comment-id:4318450886 -->
Sorry, this was an accidental duplicate submission on my side. #2009 has the canonical content, please track that one and feel free to close this.
Sorry, this was an accidental duplicate submission on my side. #2009 has the canonical content, please track that one and feel free to close this.
<!-- github-comment-id:4318626079 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
CString::ReplaceandCString::Splitloop forever on empty separatorTwo sibling library-API bugs in
CString. Same shape as #1994: a missinginput guard turns a degenerate argument into UB / unbounded loop. No
in-tree caller passes the bad argument today, but the public API is exposed
to module authors and to anyone embedding libznc.
1)
CString::Replace: emptysReplacecauses pointer underflow + OOMWhen
sReplace.empty(),strncmp(_, _, 0) == 0always matches, thearithmetic at 659 underflows
size_ttoSIZE_MAX, thep++at 665returns to the same byte, and the loop appends
sWithindefinitely untilASan kills the process for OOM.
PoC (3-second guard prevents the OOM kill):
2)
CString::Split: emptysDelim+bAllowEmpty=falseinfinite-loopsSame pattern, two locations:
strncasecmp(_, _, 0)is unconditionally 0;pnever advances; the loopspins on the CPU forever. No allocation, so this hangs rather than OOMs.
PoC:
Reachability
I grepped every caller in
src/andmodules/and didn't find one thatpasses an empty
sReplaceor emptysDelim. So this is the same"library-API correctness, not currently reachable" framing as #1994. I'm
just bringing the contracts of these two functions in line with what
callers expect.
Fix
Or, more aggressively, bail out at the top of
SplitifsDelim.empty().Happy to PR with a couple of regression tests in
test/StringTest.cppmirroring #1995.
Same as #2009 ?
Why you opening and closing the same issue as your other issues? And if you do, at least a link would be useful with explanation
Sorry, this was an accidental duplicate submission on my side. #2009 has the canonical content, please track that one and feel free to close this.