diff --git a/rpcs3/Crypto/unedat.cpp b/rpcs3/Crypto/unedat.cpp index 728b6fc9fe..cc577392cb 100644 --- a/rpcs3/Crypto/unedat.cpp +++ b/rpcs3/Crypto/unedat.cpp @@ -839,7 +839,7 @@ bool EDATADecrypter::ReadHeader() // Type 2: Use key from RAP file (RIF key). (also used for type 1 at the moment) else { - const std::string rap_path = rpcs3::utils::get_rap_file_path(npdHeader.content_id); + const std::string rap_path = rpcs3::utils::get_rap_file_path(npdHeader.get_content_id()); if (fs::file rap{rap_path}; rap && rap.size() >= sizeof(dec_key)) { diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index e049c44aa9..bebe77799b 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -49,6 +49,12 @@ struct NPD_HEADER u8 dev_hash[0x10]; s64 activate_time; s64 expire_time; + + std::string get_content_id() const + { + const std::string_view id{content_id, sizeof(content_id)}; + return std::string{id.substr(0, id.find_first_of('\0'))}; + } }; struct EDAT_HEADER diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index e6b166da24..fe4f8e3f6f 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -323,7 +323,7 @@ void supplemental_header::Show() const self_log.notice("Version: 0x%08x", PS3_npdrm_header.npd.version); self_log.notice("License: 0x%08x", PS3_npdrm_header.npd.license); self_log.notice("Type: 0x%08x", PS3_npdrm_header.npd.type); - self_log.notice("ContentID: %s", PS3_npdrm_header.npd.content_id); + self_log.notice("ContentID: %s", PS3_npdrm_header.npd.get_content_id()); self_log.notice("Digest: %s", PS3_npdrm_header.npd.digest); self_log.notice("Inverse digest: %s", PS3_npdrm_header.npd.title_hash); self_log.notice("XOR digest: %s", PS3_npdrm_header.npd.dev_hash); @@ -1065,7 +1065,7 @@ bool SELFDecrypter::DecryptNPDRM(u8 *metadata, u32 metadata_size) if (npd->license == 1) // Network license. { // Try to find a RAP file to get the key. - if (!GetKeyFromRap(npd->content_id, npdrm_key)) + if (!GetKeyFromRap(npd->get_content_id(), npdrm_key)) { self_log.error("Can't decrypt network NPDRM!"); return false; @@ -1074,7 +1074,7 @@ bool SELFDecrypter::DecryptNPDRM(u8 *metadata, u32 metadata_size) else if (npd->license == 2) // Local license. { // Try to find a RAP file to get the key. - if (!GetKeyFromRap(npd->content_id, npdrm_key)) + if (!GetKeyFromRap(npd->get_content_id(), npdrm_key)) { self_log.error("Can't find RAP file for NPDRM decryption!"); return false; @@ -1283,14 +1283,13 @@ fs::file SELFDecrypter::MakeElf(bool isElf32) return e; } -bool SELFDecrypter::GetKeyFromRap(const char* content_id, u8* npdrm_key) +bool SELFDecrypter::GetKeyFromRap(std::string_view content_id, u8* npdrm_key) { // Set empty RAP key. std::array rap_key {}; // Try to find a matching RAP file under exdata folder. - const std::string ci_str = content_id; - const std::string rap_path = rpcs3::utils::get_rap_file_path(ci_str); + const std::string rap_path = rpcs3::utils::get_rap_file_path(content_id); // Open the RAP file and read the key. const fs::file rap_file(rap_path); @@ -1303,7 +1302,7 @@ bool SELFDecrypter::GetKeyFromRap(const char* content_id, u8* npdrm_key) return false; } - self_log.notice("Loading RAP file %s.rap", ci_str); + self_log.notice("Loading RAP file %s.rap", content_id); if (rap_file.read(rap_key.data(), rap_key.size()) != rap_key.size()) { diff --git a/rpcs3/Crypto/unself.h b/rpcs3/Crypto/unself.h index 22eafb074b..beae02500b 100644 --- a/rpcs3/Crypto/unself.h +++ b/rpcs3/Crypto/unself.h @@ -478,7 +478,7 @@ public: bool DecryptData(); bool DecryptNPDRM(u8 *metadata, u32 metadata_size); const NPD_HEADER* GetNPDHeader() const; - static bool GetKeyFromRap(const char *content_id, u8 *npdrm_key); + static bool GetKeyFromRap(std::string_view content_id, u8 *npdrm_key); private: template diff --git a/rpcs3/Emu/Cell/Modules/cellGame.cpp b/rpcs3/Emu/Cell/Modules/cellGame.cpp index d7073a4e47..d9336fe16c 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGame.cpp @@ -1313,7 +1313,7 @@ error_code cellGameDeleteGameData(vm::cptr dirName) { cellGame.warning("cellGameDeleteGameData(dirName=%s)", dirName); - if (!dirName) + if (!dirName || sysutil_check_name_string(dirName.get_ptr(), 1, CELL_GAME_DIRNAME_SIZE) != 0) { return CELL_GAME_ERROR_PARAM; } diff --git a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp index 6ce57e294f..1030dbc592 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp @@ -17,7 +17,22 @@ bool music_selection_context::set(const CellMusicSelectionContext& in) } constexpr u32 pos = sizeof(magic); - hash = &in.data[pos]; + + const std::string_view data{&in.data[pos], sizeof(in.data) - pos}; + const std::string_view new_hash = data.substr(0, data.find_first_of('\0')); + + const bool is_valid_hash = !new_hash.empty() && std::all_of(new_hash.begin(), new_hash.end(), [](char c) + { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'; + }); + + if (!is_valid_hash) + { + cellMusicSelectionContext.error("Invalid music selection context hash, context = %s", context_to_hex(in)); + return false; + } + + hash = std::string{new_hash}; return load_playlist(); } @@ -35,7 +50,7 @@ CellMusicSelectionContext music_selection_context::get() const std::memset(out.data, 0, CELL_MUSIC_SELECTION_CONTEXT_SIZE); std::memcpy(out.data, magic, sizeof(magic)); pos += sizeof(magic); - std::memcpy(&out.data[pos], hash.c_str(), hash.size()); + std::memcpy(&out.data[pos], hash.data(), hash.size()); return out; } diff --git a/rpcs3/Emu/Cell/Modules/cellRec.cpp b/rpcs3/Emu/Cell/Modules/cellRec.cpp index 511cc7c852..cef43e4f7b 100644 --- a/rpcs3/Emu/Cell/Modules/cellRec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellRec.cpp @@ -1333,13 +1333,13 @@ error_code cellRecClose(s32 isDiscard) rec.sink->stop(true); } - if (fs::is_file(rec.param.filename)) + if (const std::string local_path = vfs::get(rec.param.filename); fs::is_file(local_path)) { cellRec.warning("cellRecClose: removing discarded recording '%s'", rec.param.filename); - if (!fs::remove_file(rec.param.filename)) + if (!fs::remove_file(local_path)) { - cellRec.error("cellRecClose: failed to remove recording '%s'", rec.param.filename); + cellRec.error("cellRecClose: failed to remove recording '%s' (%s)", rec.param.filename, fs::g_tls_error); } } } diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 22086bdf5c..5f6fd0eb76 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -214,6 +214,16 @@ int check_filename(std::string_view file_path, bool disallow_system_files, bool return 0; } +static bool is_valid_dir_name(const std::string& dir_name) +{ + if (dir_name.empty() || dir_name.find_first_of('\0') != umax) + { + return false; + } + + return sysutil_check_name_string(dir_name.c_str(), 1, CELL_SAVEDATA_DIRNAME_SIZE) == 0; +} + static std::vector get_save_entries(const std::string& base_dir, const std::string& prefix) { std::vector save_entries; @@ -251,6 +261,12 @@ static std::vector get_save_entries(const std::string& base_dir, save_entry.subtitle = psf::get_string(psf, "SUB_TITLE"); save_entry.details = psf::get_string(psf, "DETAIL"); + if (!is_valid_dir_name(save_entry.dirName)) + { + cellSaveData.error("Savedata '%s' has an invalid SAVEDATA_DIRECTORY entry ('%s')", entry.name, save_entry.dirName); + continue; + } + for (const auto& entry2 : fs::dir(base_dir + entry.name)) { if (entry2.is_directory || check_filename(vfs::unescape(entry2.name), false, true)) @@ -848,6 +864,12 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v save_entry2.subtitle = psf::get_string(psf, "SUB_TITLE"); save_entry2.details = psf::get_string(psf, "DETAIL"); + if (!is_valid_dir_name(save_entry2.dirName)) + { + cellSaveData.error("Savedata '%s' has an invalid SAVEDATA_DIRECTORY entry ('%s')", entry.name, save_entry2.dirName); + break; + } + for (const auto& entry2 : fs::dir(base_dir + entry.name)) { if (entry2.is_directory || check_filename(vfs::unescape(entry2.name), false, true)) @@ -1468,6 +1490,12 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v save_entry.escaped = vfs::escape(save_entry.dirName); } + if (!is_valid_dir_name(save_entry.dirName)) + { + cellSaveData.error("savedata_op(): invalid savedata directory name ('%s')", save_entry.dirName); + return {CELL_SAVEDATA_ERROR_BROKEN, save_entry.dirName}; + } + const std::string dir_path = base_dir + save_entry.escaped + "/"; const std::string old_path = base_dir + ".backup_" + save_entry.escaped + "/"; const std::string new_path = base_dir + ".working_" + save_entry.escaped + "/"; @@ -2215,7 +2243,7 @@ static NEVER_INLINE error_code savedata_get_list_item(vm::cptr dirName, vm return {CELL_SAVEDATA_ERROR_PARAM, "107"}; } - switch (sysutil_check_name_string(dirName.get_ptr(), 1, CELL_SAVEDATA_DIRLIST_MAX)) + switch (sysutil_check_name_string(dirName.get_ptr(), 1, CELL_SAVEDATA_DIRNAME_SIZE)) { case -1: { diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index 1e09b8f493..8fe1f7d5f6 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -699,7 +699,7 @@ error_code npDrmIsAvailable(vm::cptr k_licensee_addr, vm::cptr drm_pat } else { - const std::string rap_file = rpcs3::utils::get_rap_file_path(npd.content_id); + const std::string rap_file = rpcs3::utils::get_rap_file_path(npd.get_content_id()); if (fs::file rap_fd{rap_file}) { diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 931e0c22d9..909cf17cc0 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -460,7 +460,7 @@ lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string filename.remove_prefix(cell_fs_path.size()); const bool is_path = filename.starts_with("/"sv); - std::string mp_name = is_path ? std::string{get_path_root_and_trail(filename).first} : std::string(filename); + std::string mp_name = is_path ? "/" + std::string{get_path_root_and_trail(filename).first} : std::string(filename); const auto check_mp = [&]() { @@ -521,9 +521,6 @@ lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string *vfs_path = g_cfg_vfs.get_dev_flash3(); else *vfs_path = {}; - - if (is_path && !is_cell_fs_path && !vfs_path->empty()) - vfs_path->append(filename.substr(mp_name.size())); } return result; diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h index f3ccfeee67..df34a0c5fa 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ b/rpcs3/Emu/Cell/lv2/sys_fs.h @@ -246,8 +246,8 @@ public: lv2_fs_object& operator=(const lv2_fs_object&) = delete; - // Get the device's root path (e.g. "/dev_hdd0") from a given path - // Cut the trail and return it in seccond argument + // Get the device's root path (e.g. "dev_hdd0") from a given path + // Cut the trail and return it in second argument static std::pair get_path_root_and_trail(std::string_view path); // Get the device's root path (e.g. "/dev_hdd0") from a given path diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index f95d65fd69..9e39827b30 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -378,8 +378,27 @@ namespace rpcs3::utils return file_list; } + static bool is_valid_content_id(std::string_view content_id) + { + if (content_id.empty() || content_id.size() > 0x30) + { + return false; + } + + return std::all_of(content_id.begin(), content_id.end(), [](char c) + { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'; + }); + } + std::string get_rap_file_path(const std::string_view& rap) { + if (!is_valid_content_id(rap)) + { + sys_log.error("get_rap_file_path(): invalid content id '%s'", rap); + return {}; + } + const std::string home_dir = get_hdd0_dir() + "home"; std::string rap_path; @@ -402,6 +421,12 @@ namespace rpcs3::utils std::string get_c00_unlock_edat_path(const std::string_view& content_id) { + if (!is_valid_content_id(content_id)) + { + sys_log.error("get_c00_unlock_edat_path(): invalid content id '%s'", content_id); + return {}; + } + const std::string home_dir = get_hdd0_dir() + "home"; std::string edat_path; @@ -448,7 +473,7 @@ namespace rpcs3::utils return false; } - std::string edat_content_id = npd.content_id; + std::string edat_content_id{npd.get_content_id()}; if (edat_content_id != content_id) { diff --git a/rpcs3/Loader/TRP.cpp b/rpcs3/Loader/TRP.cpp index 452b7c9384..541b4d95f7 100644 --- a/rpcs3/Loader/TRP.cpp +++ b/rpcs3/Loader/TRP.cpp @@ -1,11 +1,18 @@ #include "stdafx.h" #include "Emu/VFS.h" +#include "Emu/System.h" #include "TRP.h" #include "Crypto/sha1.h" #include "Utilities/StrUtil.h" LOG_CHANNEL(trp_log, "Trophy"); +static std::string get_entry_name(const TRPEntry& entry) +{ + const std::string_view name{entry.name, sizeof(entry.name)}; + return std::string{name.substr(0, name.find_first_of('\0'))}; +} + TRPLoader::TRPLoader(const fs::file& f) : m_file(f) { @@ -51,7 +58,16 @@ bool TRPLoader::Install(std::string_view dest, bool /*show*/) } // Create the file in the temporary directory - const std::string filename = temp + '/' + vfs::escape(entry.name); + const std::string entry_name = get_entry_name(entry); + const std::string filename = temp + '/' + vfs::escape(entry_name, true); + + if (!Emu.IsPathInsideDir(filename, temp, false)) + { + trp_log.error("Error extracting %s from TRP: target path '%s' would be extracted outside of '%s'", entry_name, filename, temp); + success = false; + break; + } + success = fs::write_file(filename, fs::create + fs::excl, buffer); if (!success) { @@ -160,7 +176,7 @@ bool TRPLoader::LoadHeader(bool show) { for (const auto& entry : m_entries) { - trp_log.notice("TRP entry #%u: %s", &entry - m_entries.data(), entry.name); + trp_log.notice("TRP entry #%u: %s", &entry - m_entries.data(), get_entry_name(entry)); } } @@ -184,7 +200,7 @@ bool TRPLoader::ContainsEntry(std::string_view filename) for (const TRPEntry& entry : m_entries) { - if (entry.name == filename) + if (get_entry_name(entry) == filename) { return true; } @@ -202,7 +218,7 @@ void TRPLoader::RemoveEntry(std::string_view filename) std::vector::iterator i = m_entries.begin(); while (i != m_entries.end()) { - if (i->name == filename) + if (get_entry_name(*i) == filename) { i = m_entries.erase(i); } @@ -222,7 +238,7 @@ void TRPLoader::RenameEntry(std::string_view oldname, std::string_view newname) for (TRPEntry& entry : m_entries) { - if (entry.name == oldname) + if (get_entry_name(entry) == oldname) { strcpy_trunc(entry.name, newname); }