mirror of
https://github.com/PCSX2/pcsx2.git
synced 2025-12-16 04:08:48 +00:00
GameDB: Fix infinite loops caused by UB when YAML parsing fails
This commit is contained in:
parent
07bc2fa452
commit
0ce312c1c3
@ -7,7 +7,6 @@ endif(NOT TOP_CMAKE_WAS_SOURCED)
|
||||
|
||||
add_library(common)
|
||||
|
||||
# x86emitter sources
|
||||
target_sources(common PRIVATE
|
||||
AlignedMalloc.cpp
|
||||
Assertions.cpp
|
||||
@ -34,9 +33,9 @@ target_sources(common PRIVATE
|
||||
Timer.cpp
|
||||
WAVWriter.cpp
|
||||
WindowInfo.cpp
|
||||
YAML.cpp
|
||||
)
|
||||
|
||||
# x86emitter headers
|
||||
target_sources(common PRIVATE
|
||||
AlignedMalloc.h
|
||||
Assertions.h
|
||||
@ -81,6 +80,7 @@ target_sources(common PRIVATE
|
||||
WAVWriter.h
|
||||
WindowInfo.h
|
||||
WrappedMemCopy.h
|
||||
YAML.h
|
||||
)
|
||||
|
||||
if(_M_X86)
|
||||
@ -208,6 +208,7 @@ target_link_libraries(common PRIVATE
|
||||
target_link_libraries(common PUBLIC
|
||||
fmt::fmt
|
||||
fast_float
|
||||
rapidyaml::rapidyaml
|
||||
)
|
||||
|
||||
fixup_file_properties(common)
|
||||
|
||||
43
common/YAML.cpp
Normal file
43
common/YAML.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "YAML.h"
|
||||
|
||||
#include <csetjmp>
|
||||
#include <cstdlib>
|
||||
|
||||
struct RapidYAMLContext
|
||||
{
|
||||
std::jmp_buf env;
|
||||
Error* error = nullptr;
|
||||
};
|
||||
|
||||
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error)
|
||||
{
|
||||
RapidYAMLContext context;
|
||||
context.error = error;
|
||||
|
||||
ryml::Callbacks callbacks;
|
||||
callbacks.m_user_data = static_cast<void*>(&context);
|
||||
callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location location, void* user_data) {
|
||||
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
|
||||
|
||||
Error::SetString(context->error, std::string(msg, msg_len));
|
||||
std::longjmp(context->env, 1);
|
||||
};
|
||||
|
||||
ryml::EventHandlerTree event_handler(callbacks);
|
||||
ryml::Parser parser(&event_handler);
|
||||
|
||||
ryml::Tree tree;
|
||||
|
||||
// The only options RapidYAML provides for recovering from errors are
|
||||
// throwing an exception or using setjmp/longjmp. Since we have exceptions
|
||||
// disabled we have to use the latter option.
|
||||
if (setjmp(context.env))
|
||||
return std::nullopt;
|
||||
|
||||
ryml::parse_in_arena(&parser, file_name, yaml, &tree);
|
||||
|
||||
return tree;
|
||||
}
|
||||
18
common/YAML.h
Normal file
18
common/YAML.h
Normal file
@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Error.h"
|
||||
|
||||
#include "ryml_std.hpp"
|
||||
#include "ryml.hpp"
|
||||
#include "ryml.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
/// Parse a YAML file with RapidYAML, and use setjmp/longjmp to recover from
|
||||
/// parsing errors (as is recommended by the documentation for cases where
|
||||
/// exceptions are disabled). The file_name parameter is only used for error
|
||||
/// messages, which are returned via the error parameter.
|
||||
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error);
|
||||
@ -36,10 +36,12 @@
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fast_float\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\include</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<ForcedIncludeFiles>PrecompiledHeader.h</ForcedIncludeFiles>
|
||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||
<ObjectFileName>$(IntDir)%(RelativeDir)</ObjectFileName>
|
||||
<PreprocessorDefinitions>C4_NO_DEBUG_BREAK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@ -71,6 +73,7 @@
|
||||
<ClCompile Include="Timer.cpp" />
|
||||
<ClCompile Include="WAVWriter.cpp" />
|
||||
<ClCompile Include="WindowInfo.cpp" />
|
||||
<ClCompile Include="YAML.cpp" />
|
||||
<ClCompile Include="Perf.cpp" />
|
||||
<ClCompile Include="PrecompiledHeader.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@ -157,6 +160,7 @@
|
||||
<ClInclude Include="Timer.h" />
|
||||
<ClInclude Include="WAVWriter.h" />
|
||||
<ClInclude Include="WindowInfo.h" />
|
||||
<ClInclude Include="YAML.h" />
|
||||
<ClInclude Include="Threading.h" />
|
||||
<ClInclude Include="emitter\implement\avx.h" />
|
||||
<ClInclude Include="emitter\implement\bmi.h" />
|
||||
|
||||
@ -127,6 +127,9 @@
|
||||
<ClCompile Include="SmallString.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="YAML.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AlignedMalloc.h">
|
||||
@ -335,6 +338,9 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="SingleRegisterTypes.h" />
|
||||
<ClInclude Include="FPControl.h" />
|
||||
<ClInclude Include="YAML.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
@ -349,4 +355,4 @@
|
||||
<Filter>Source Files</Filter>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@ -1140,7 +1140,6 @@ target_link_libraries(PCSX2_FLAGS INTERFACE
|
||||
common
|
||||
imgui
|
||||
fmt::fmt
|
||||
rapidyaml::rapidyaml
|
||||
libchdr
|
||||
libzip::zip
|
||||
cpuinfo
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/YAML.h"
|
||||
|
||||
#include <sstream>
|
||||
#include "ryml_std.hpp"
|
||||
@ -357,8 +358,7 @@ static const char* s_round_modes[static_cast<u32>(FPRoundMode::MaxCount)] = {
|
||||
"Nearest",
|
||||
"NegativeInfinity",
|
||||
"PositiveInfinity",
|
||||
"Chop"
|
||||
};
|
||||
"Chop"};
|
||||
|
||||
static const char* s_gs_hw_fix_names[] = {
|
||||
"autoFlush",
|
||||
@ -930,7 +930,7 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
|
||||
Host::AddKeyedOSDMessage("HWFixesWarning",
|
||||
fmt::format(ICON_FA_WAND_MAGIC_SPARKLES " {}\n{}",
|
||||
TRANSLATE_SV("GameDatabase", "Manual GS hardware renderer fixes are enabled, automatic fixes were not applied:"),
|
||||
disabled_fixes),
|
||||
disabled_fixes),
|
||||
Host::OSD_ERROR_DURATION);
|
||||
}
|
||||
else
|
||||
@ -941,25 +941,28 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
|
||||
|
||||
void GameDatabase::initDatabase()
|
||||
{
|
||||
ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
|
||||
rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void* userdata) {
|
||||
Console.Error(fmt::format("[GameDB YAML] Parsing error at {}:{} (bufpos={}): {}",
|
||||
loc.line, loc.col, loc.offset, std::string_view(msg, msg_len)));
|
||||
};
|
||||
ryml::set_callbacks(rymlCallbacks);
|
||||
ryml::set_error_callback([](const char* msg, size_t msg_size) {
|
||||
Console.Error(fmt::format("[GameDB YAML] Internal Parsing error: {}", std::string_view(msg, msg_size)));
|
||||
});
|
||||
const std::string path(Path::Combine(EmuFolders::Resources, GAMEDB_YAML_FILE_NAME));
|
||||
const std::string name(GAMEDB_YAML_FILE_NAME);
|
||||
|
||||
auto buf = FileSystem::ReadFileToString(Path::Combine(EmuFolders::Resources, GAMEDB_YAML_FILE_NAME).c_str());
|
||||
if (!buf.has_value())
|
||||
const std::optional<std::string> buffer = FileSystem::ReadFileToString(path.c_str());
|
||||
if (!buffer.has_value())
|
||||
{
|
||||
Console.Error("GameDB: Unable to open GameDB file, file does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(buf.value()));
|
||||
ryml::NodeRef root = tree.rootref();
|
||||
const ryml::csubstr yaml = ryml::to_csubstr(*buffer);
|
||||
|
||||
Error error;
|
||||
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error);
|
||||
if (!tree.has_value())
|
||||
{
|
||||
Console.ErrorFmt("GameDB: Failed to parse game database file {}:", path);
|
||||
Console.Error(error.GetDescription());
|
||||
return;
|
||||
}
|
||||
|
||||
ryml::NodeRef root = tree->rootref();
|
||||
|
||||
for (const ryml::NodeRef& n : root.children())
|
||||
{
|
||||
@ -971,7 +974,7 @@ void GameDatabase::initDatabase()
|
||||
// However, YAML's keys are as expected case-sensitive, so we have to explicitly do our own duplicate checking
|
||||
if (s_game_db.count(serial) == 1)
|
||||
{
|
||||
Console.Error(fmt::format("GameDB: Duplicate serial '{}' found in GameDB. Skipping, Serials are case-insensitive!", serial));
|
||||
Console.ErrorFmt("GameDB: Duplicate serial '{}' found in GameDB. Skipping, Serials are case-insensitive!", serial);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -980,8 +983,6 @@ void GameDatabase::initDatabase()
|
||||
parseAndInsert(serial, n);
|
||||
}
|
||||
}
|
||||
|
||||
ryml::reset_callbacks();
|
||||
}
|
||||
|
||||
void GameDatabase::ensureLoaded()
|
||||
@ -1069,7 +1070,7 @@ static bool parseHashDatabaseEntry(const ryml::NodeRef& node)
|
||||
{
|
||||
if (!n.is_map() || !n.has_child("size") || !n.has_child("md5"))
|
||||
{
|
||||
Console.Error(fmt::format("[HashDatabase] Incomplete hash definition in {}", entry.name));
|
||||
Console.ErrorFmt("[HashDatabase] Incomplete hash definition in {}", entry.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1080,12 +1081,12 @@ static bool parseHashDatabaseEntry(const ryml::NodeRef& node)
|
||||
|
||||
if (!th.parseHash(md5))
|
||||
{
|
||||
Console.Error(fmt::format("[HashDatabase] Failed to parse hash in {}: '{}'", entry.name, md5));
|
||||
Console.ErrorFmt("[HashDatabase] Failed to parse hash in {}: '{}'", entry.name, md5);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.tracks.empty() && s_track_hash_to_entry_map.find(th) != s_track_hash_to_entry_map.end())
|
||||
Console.Warning(fmt::format("[HashDatabase] Duplicate first track hash in {}", entry.name));
|
||||
Console.WarningFmt("[HashDatabase] Duplicate first track hash in {}", entry.name);
|
||||
|
||||
entry.tracks.push_back(th);
|
||||
s_track_hash_to_entry_map.emplace(th, index);
|
||||
@ -1100,27 +1101,30 @@ bool GameDatabase::loadHashDatabase()
|
||||
if (!s_hash_database.empty())
|
||||
return true;
|
||||
|
||||
ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
|
||||
rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void*) {
|
||||
Console.Error(fmt::format(
|
||||
"[HashDatabase YAML] Parsing error at {}:{} (bufpos={}): {}", loc.line, loc.col, loc.offset, msg));
|
||||
};
|
||||
ryml::set_callbacks(rymlCallbacks);
|
||||
ryml::set_error_callback([](const char* msg, size_t msg_size) {
|
||||
Console.Error(fmt::format("[HashDatabase YAML] Internal Parsing error: {}", std::string_view(msg, msg_size)));
|
||||
});
|
||||
|
||||
Common::Timer load_timer;
|
||||
|
||||
auto buf = FileSystem::ReadFileToString(Path::Combine(EmuFolders::Resources, HASHDB_YAML_FILE_NAME).c_str());
|
||||
if (!buf.has_value())
|
||||
const std::string path(Path::Combine(EmuFolders::Resources, HASHDB_YAML_FILE_NAME));
|
||||
const std::string name(HASHDB_YAML_FILE_NAME);
|
||||
|
||||
std::optional<std::string> buffer = FileSystem::ReadFileToString(path.c_str());
|
||||
if (!buffer.has_value())
|
||||
{
|
||||
Console.Error("GameDB: Unable to open hash database file, file does not exist.");
|
||||
Console.Error("[HashDatabase] Unable to open hash database file, file does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(buf.value()));
|
||||
ryml::NodeRef root = tree.rootref();
|
||||
ryml::csubstr yaml = ryml::to_csubstr(*buffer);
|
||||
|
||||
Error error;
|
||||
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error);
|
||||
if (!tree.has_value())
|
||||
{
|
||||
Console.ErrorFmt("[HashDatabase] Failed to parse hash database file {}:", path);
|
||||
Console.Error(error.GetDescription());
|
||||
return false;
|
||||
}
|
||||
|
||||
ryml::NodeRef root = tree->rootref();
|
||||
|
||||
bool okay = true;
|
||||
for (const ryml::NodeRef& n : root.children())
|
||||
@ -1132,7 +1136,6 @@ bool GameDatabase::loadHashDatabase()
|
||||
}
|
||||
}
|
||||
|
||||
ryml::reset_callbacks();
|
||||
if (!okay)
|
||||
{
|
||||
s_track_hash_to_entry_map.clear();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user