CwCheatScreen: fix crash on malformed cheat database lines

GetLineNoNewline() computed `line + strlen(line) - 1` to strip a
trailing newline; fgets() doesn't stop at embedded NUL bytes, so a
line starting with one made strlen() return 0, pointing `end` one byte
before the buffer (a small OOB read, and conditionally an OOB write if
that byte happened to equal '\n').

ImportCheats() also called substr(4) on a "_C" cheat-name line without
checking its length first - a line that's just "_C"/"_C0"/"_C1" with
no name is shorter than 4 characters, and substr() throws
std::out_of_range, uncaught anywhere in this call chain, crashing the
app.

Both are reachable by importing a shared/downloaded cheat database
file (the normal way users add cheats), which could be corrupted or
maliciously crafted.
This commit is contained in:
Henrik Rydgård committed 2026-08-12 09:27:47 +02:00
1 parent b68e0c0896
commit 7f7fe3c712
1 file changed
+9 -2
+9 -2
View File
@@ -398,6 +398,10 @@ static char *GetLineNoNewline(char *temp, int sz, FILE *fp) {
char *line = fgets(temp, sz, fp);
if (!line)
return nullptr;
// fgets() doesn't stop at embedded NUL bytes, so a "line" starting with one would
// otherwise make strlen() return 0 here, and `end` would point before the buffer.
if (line[0] == '\0')
return line;
// If the last character is \n, just make it the terminator.
char *end = line + strlen(line) - 1;
@@ -487,8 +491,11 @@ bool CwCheatScreen::ImportCheats(const Path &cheatFile, int *cheatsFound) {
parseGameEntry = gameID == line;
parseCheatEntry = false;
} else if (parseGameEntry && line[0] == '_' && line[1] == 'C') {
// Test if cheat already exists.
parseCheatEntry = !HasCheatWithName(std::string(line).substr(4));
// Test if cheat already exists. A malformed/truncated line (e.g. just
// "_C" or "_C0" with no name) is shorter than 4 chars - substr(4) would
// throw std::out_of_range, uncaught here, crashing the whole app.
std::string lineStr(line);
parseCheatEntry = lineStr.size() < 4 || !HasCheatWithName(lineStr.substr(4));
}
if (!parseGameEntry) {