Skip savestate migration if game ID strings contain a path separator

DISC_ID/DISC_VERSION/homebrewName are attacker-controlled and were used
unsanitized to build migration rename paths. Skip the migration step if
any of them contain a '/' or '\' so the destination can't escape the
savestate directory.
This commit is contained in:
Henrik Rydgård committed 2026-08-01 11:41:24 +02:00
1 parent a7b1b319ce
commit c0f5b3d3ee
1 file changed
+9 -1
+9 -1
View File
@@ -425,8 +425,16 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string_view discId, bool load
// Migrate old save states from old versions of fake game IDs.
// Ugh, this might actually be slow on Android.
// The strings here are attacker-controlled (from PARAM.SFO / filenames), so
// if any of them contain a path separator, skip the migration to avoid
// building traversal paths.
const bool anyPathSeparator =
discID.find('/') != std::string::npos || discID.find('\\') != std::string::npos ||
discVersion.find('/') != std::string::npos || discVersion.find('\\') != std::string::npos ||
homebrewName.find('/') != std::string::npos || homebrewName.find('\\') != std::string::npos ||
madeUpID.find('/') != std::string::npos || madeUpID.find('\\') != std::string::npos;
const Path savestateDir = GetSysDirectory(DIRECTORY_SAVESTATE);
for (int i = 0; i < 5; ++i) {
for (int i = 0; i < 5 && !anyPathSeparator; ++i) {
Path newPrefix = savestateDir / StringFromFormat("%s_%s_%d", discID.c_str(), discVersion.c_str(), i);
Path oldNamePrefix = savestateDir / StringFromFormat("%s_%d", homebrewName.c_str(), i);
Path oldIDPrefix = savestateDir / StringFromFormat("%s_1.00_%d", madeUpID.c_str(), i);