Reject path traversal in savedata name fields

gameName/saveName/fileName and the saveNameList entries are
guest-controlled and get concatenated into host filesystem paths, so a
crafted request could escape the save directory with ../ sequences.

- PSPSaveDialog::Init rejects requests whose name fields contain a path
  separator ('/' or '\') or are bare dot components.
- SavedataParam::SetPspParam rejects saveNameList entries the same way.
This commit is contained in:
Henrik Rydgård committed 2026-08-01 13:16:21 +02:00
1 parent a67fc2300d
commit 779cae6232
2 files changed
+34

No files matched your search

+20
View File
@@ -136,6 +136,26 @@ int PSPSaveDialog::Init(int paramAddr) {
Memory::Memcpy(&request, requestAddr, size);
Memory::Memcpy(&originalRequest, requestAddr, size);
// gameName/saveName/fileName become parts of host filesystem paths.
// Reject path separators (either direction) and bare dot components so a
// crafted request can't escape the save directory (path traversal).
auto hasPathTraversal = [](const char *field, size_t fieldSize) {
size_t len = 0;
while (len < fieldSize && field[len] != 0)
len++;
for (size_t i = 0; i < len; i++) {
if (field[i] == '/' || field[i] == '\\')
return true;
}
return (len == 1 && field[0] == '.') || (len == 2 && field[0] == '.' && field[1] == '.');
};
if (hasPathTraversal(request.gameName, sizeof(request.gameName)) ||
hasPathTraversal(request.saveName, sizeof(request.saveName)) ||
hasPathTraversal(request.fileName, sizeof(request.fileName))) {
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilitySavedataInitStart: path separator in name fields");
return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE;
}
param.SetIgnoreTextures(IsNotVisibleAction((SceUtilitySavedataType)(u32)request.mode));
param.ClearSFOCache();
int retval = param.SetPspParam(&request);
+14
View File
@@ -1547,6 +1547,20 @@ int SavedataParam::SetPspParam(SceUtilitySavedataParam *param) {
// Get number of fileName in array
saveDataListCount = 0;
while (saveNameListData[saveDataListCount][0] != 0) {
// saveName entries become part of host filesystem paths; reject
// path separators and bare dot components (path traversal).
const std::string_view entry = StringViewFromFixedSizeField(saveNameListData[saveDataListCount]);
bool hasSeparator = false;
for (char c : entry) {
if (c == '/' || c == '\\') {
hasSeparator = true;
break;
}
}
if (hasSeparator || entry == "." || entry == "..") {
ERROR_LOG(Log::sceUtility, "SavedataParam: invalid saveName in list: %s", std::string(entry).c_str());
return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE;
}
saveDataListCount++;
}