Merge pull request #3784 from MrMha/fix/param-json-utf8
fix(libkernel): reject malformed UTF-8 in param JSON
This commit is contained in:
commit
6566c9c8c6
|
|
@ -95,6 +95,27 @@ std::uint32_t parseHex4(const std::string& text, std::size_t& position) {
|
|||
return value;
|
||||
}
|
||||
|
||||
void appendRawUtf8(const std::string& text, std::size_t& position, unsigned char first, std::string& result) {
|
||||
unsigned count;
|
||||
std::uint32_t codepoint;
|
||||
std::uint32_t minimum;
|
||||
if (first >= 0xC2 && first <= 0xDF) { count = 1; codepoint = first & 0x1F; minimum = 0x80; }
|
||||
else if (first >= 0xE0 && first <= 0xEF) { count = 2; codepoint = first & 0x0F; minimum = 0x800; }
|
||||
else if (first >= 0xF0 && first <= 0xF4) { count = 3; codepoint = first & 0x07; minimum = 0x10000; }
|
||||
else throw std::runtime_error("invalid UTF-8 in param.json");
|
||||
if (count > text.size() - position) throw std::runtime_error("truncated UTF-8 in param.json");
|
||||
for (unsigned index = 0; index < count; ++index) {
|
||||
const auto byte = static_cast<unsigned char>(text[position + index]);
|
||||
if ((byte & 0xC0) != 0x80) throw std::runtime_error("invalid UTF-8 continuation in param.json");
|
||||
codepoint = (codepoint << 6) | (byte & 0x3F);
|
||||
}
|
||||
if (codepoint < minimum || codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF))
|
||||
throw std::runtime_error("invalid UTF-8 codepoint in param.json");
|
||||
result.push_back(static_cast<char>(first));
|
||||
result.append(text, position, count);
|
||||
position += count;
|
||||
}
|
||||
|
||||
std::string parseJsonString(const std::string& text, std::size_t& position) {
|
||||
expectChar(text, position, '"');
|
||||
std::string result;
|
||||
|
|
@ -103,7 +124,11 @@ std::string parseJsonString(const std::string& text, std::size_t& position) {
|
|||
const char c = text[position++];
|
||||
if (c == '"') break;
|
||||
if (static_cast<unsigned char>(c) < 0x20) throw std::runtime_error("control character in param.json string");
|
||||
if (c != '\\') { result.push_back(c); continue; }
|
||||
if (c != '\\') {
|
||||
if (static_cast<unsigned char>(c) < 0x80) result.push_back(c);
|
||||
else appendRawUtf8(text, position, static_cast<unsigned char>(c), result);
|
||||
continue;
|
||||
}
|
||||
if (position >= text.size()) throw std::runtime_error("unterminated escape in param.json");
|
||||
const char escape = text[position++];
|
||||
switch (escape) {
|
||||
|
|
|
|||
|
|
@ -90,11 +90,34 @@ static void CheckNumberSyntax(const std::filesystem::path& path) {
|
|||
}
|
||||
}
|
||||
|
||||
static void CheckUtf8(const std::filesystem::path& path) {
|
||||
const std::string prefix = R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":")";
|
||||
const std::string suffix = R"("}}})";
|
||||
for (const auto& title : {std::string("ASCII"), std::string("\xC2\x80"), std::string("\xE3\x82\xB2\xE3\x83\xBC\xE3\x83\xA0"),
|
||||
std::string("\xF0\x9F\x8E\xAE"), std::string("\xF4\x8F\xBF\xBF")}) {
|
||||
Require(ParseText(path, prefix + title + suffix).title == title, "Valid UTF-8 title changed");
|
||||
}
|
||||
Require(ParseText(path, prefix + R"(\uD83C\uDFAE)" + suffix).title == "\xF0\x9F\x8E\xAE", "Escaped title changed");
|
||||
for (const auto& title : {std::string("\x80"), std::string("\xC0\xAF"), std::string("\xC1\xBF"), std::string("\xC2"),
|
||||
std::string("\xE0\x80\xAF"), std::string("\xED\xA0\x80"), std::string("\xE3\x82"),
|
||||
std::string("\xF0\x80\x80\xAF"), std::string("\xF4\x90\x80\x80"), std::string("\xF5\x80\x80\x80"),
|
||||
std::string("\xFE"), std::string("\xFF"), std::string("\xC2x"), std::string("\xE3\x82x")}) {
|
||||
bool rejected = false;
|
||||
try { ParseText(path, prefix + title + suffix); } catch (const std::exception&) { rejected = true; }
|
||||
Require(rejected, "Accepted malformed UTF-8 title");
|
||||
}
|
||||
bool rejected = false;
|
||||
try { ParseText(path, std::string("{\"") + "\x80" + R"(":0,)" + prefix.substr(1) + "Valid" + suffix); }
|
||||
catch (const std::exception&) { rejected = true; }
|
||||
Require(rejected, "Accepted malformed UTF-8 object key");
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto path = std::filesystem::temp_directory_path() / ("anyps5-param-json-" +
|
||||
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".json");
|
||||
int result = 0;
|
||||
try {
|
||||
CheckUtf8(path);
|
||||
CheckNumberSyntax(path);
|
||||
const std::pair<const char*, std::uint64_t> sizes[] = {
|
||||
{"", 0}, {"0", 0}, {"-0", 0}, {"0.25", 0}, {"0e3", 0}, {"0E+3", 0}, {"-0e-3", 0}, {"4096", 4096},
|
||||
|
|
|
|||
|
|
@ -202,6 +202,13 @@ if(BUILD_TESTING)
|
|||
target_include_directories(buffer_bounds_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/io/include)
|
||||
add_test(NAME buffer_bounds COMMAND buffer_bounds_tests)
|
||||
|
||||
add_test_executable(param_json_utf8_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../libs/tests/ParamJsonParser.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp)
|
||||
target_include_directories(param_json_utf8_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../libs)
|
||||
add_test(NAME param_json_utf8 COMMAND param_json_utf8_tests)
|
||||
set_tests_properties(param_json_utf8 PROPERTIES TIMEOUT 20)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_test_executable(file_writer_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/tests/FileWriterTests.cpp
|
||||
|
|
|
|||
Loading…
Reference in New Issue