From c0f5b3d3eec5c27d06fe418a71afdd49ebcdf976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 31 Jul 2026 17:39:37 +0200 Subject: [PATCH] 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. --- Core/PSPLoaders.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index f81ce2e220..3be69e371b 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -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);