diff --git a/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp index c43d98a53..aabd25ac4 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp @@ -30,6 +30,9 @@ void validatePng(const std::vector& bytes, const std::filesystem:: ((color == 2 || color == 4 || color == 6) && (depth == 8 || depth == 16)); if (!validDepth || bytes[26] != 0 || bytes[27] != 0 || bytes[28] > 1) fail("invalid IHDR encoding"); bool hasData = false; + bool dataStarted = false; + bool dataEnded = false; + bool hasPalette = false; for (std::size_t offset = 8; offset < bytes.size();) { if (bytes.size() - offset < 12) fail("truncated chunk"); const auto length = readBigEndian(bytes, offset); @@ -42,7 +45,20 @@ void validatePng(const std::vector& bytes, const std::filesystem:: } if ((crc ^ 0xffffffffu) != readBigEndian(bytes, offset + 8 + length)) fail("invalid chunk CRC"); if (type == 0x49484452 && offset != 8) fail("duplicate IHDR"); - if (type == 0x49444154 && length != 0) hasData = true; + if (type == 0x504c5445) { + if (hasPalette || dataStarted || !length || length % 3 || length > 768 || color == 0 || color == 4 || + (color == 3 && length / 3 > (1u << depth))) fail("invalid PLTE"); + hasPalette = true; + } + if (type == 0x49444154) { + if (dataEnded || (color == 3 && !hasPalette)) fail("invalid IDAT order or missing PLTE"); + dataStarted = true; + if (length != 0) hasData = true; + } else { + if (dataStarted) dataEnded = true; + if (!(type & 0x20000000u) && type != 0x49484452 && type != 0x504c5445 && type != 0x49454e44) + fail("unknown critical chunk"); + } offset += length + 12; if (type == 0x49454e44) { if (length != 0 || offset != bytes.size() || !hasData) fail("invalid IEND or missing IDAT"); diff --git a/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp b/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp index e9ec7a6cf..772189083 100644 --- a/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp +++ b/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp @@ -27,9 +27,76 @@ std::uint32_t readU32(const std::vector& data, const std::size_t o static_cast(data[offset + 2]) << 16 | static_cast(data[offset + 3]) << 24; } +std::vector chunk(const std::string& type, const std::vector& payload) { + std::vector result; + const auto append = [&result](std::uint32_t value) { + for (const auto shift : {24, 16, 8, 0}) result.push_back(static_cast(value >> shift)); + }; + append(static_cast(payload.size())); + result.insert(result.end(), type.begin(), type.end()); + result.insert(result.end(), payload.begin(), payload.end()); + std::uint32_t crc = 0xffffffffu; + for (std::size_t index = 4; index < result.size(); ++index) { + crc ^= result[index]; + for (int bit = 0; bit < 8; ++bit) crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320u : 0u); + } + append(crc ^ 0xffffffffu); + return result; +} + +void checkChunks() { + const Elfpatcher::Windows::WindowsIconResourceBuilder builder; + const auto check = [&](const std::string& name, const std::vector>& chunks, bool valid, const std::vector& header = {}) { + std::vector image(Png.begin(), Png.begin() + (header.empty() ? 33 : 8)); + if (!header.empty()) { + const auto ihdr = chunk("IHDR", header); + image.insert(image.end(), ihdr.begin(), ihdr.end()); + } + for (const auto& value : chunks) image.insert(image.end(), value.begin(), value.end()); + image.insert(image.end(), Png.end() - 12, Png.end()); + const std::filesystem::path path = std::filesystem::path(name) / "icon.png"; + std::vector sections; + bool rejected = false; + try { builder.Build(image, path, sections, 0x10000); } + catch (const Domain::RelinkerException& error) { + rejected = true; + require(std::string(error.what()).find(path.string()) != std::string::npos, "Icon failure omitted the path"); + } + require(rejected != valid, "Unexpected validation result for " + name); + require(sections.size() == (valid ? 1u : 0u), "Unexpected section mutation for " + name); + }; + const std::vector data(Png.begin() + 41, Png.begin() + 54); + const auto idat = chunk("IDAT", data); + const auto note = chunk("tEXt", {'n', 0, 'v'}); + check("unknown-critical", {chunk("ABCD", {}), idat}, false); + check("ancillary", {note, idat}, true); + const auto first = chunk("IDAT", {data.begin(), data.begin() + 6}); + const auto last = chunk("IDAT", {data.begin() + 6, data.end()}); + check("consecutive", {first, last}, true); + check("nonconsecutive", {first, note, last}, false); + const auto palette = chunk("PLTE", {255, 0, 0}); + check("late-palette", {idat, palette}, false); + check("duplicate-palette", {palette, palette, idat}, false); + check("palette", {palette, idat}, true); + check("empty-palette", {chunk("PLTE", {}), idat}, false); + check("partial-palette", {chunk("PLTE", {0, 1, 2, 3}), idat}, false); + check("large-palette", {chunk("PLTE", std::vector(771)), idat}, false); + std::vector header(Png.begin() + 16, Png.begin() + 29); + header[8] = 1; + header[9] = 3; + const auto indexed = chunk("IDAT", {0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01}); + check("indexed", {palette, indexed}, true, header); + check("indexed-missing-palette", {indexed}, false, header); + check("indexed-large-palette", {chunk("PLTE", std::vector(9)), indexed}, false, header); + header[9] = 0; + check("grayscale-palette", {palette, indexed}, false, header); +} + void run() { const Elfpatcher::Windows::WindowsIconResourceBuilder builder; + checkChunks(); + const std::filesystem::path iconPath = "valid/icon0.png"; const std::uint32_t nextRva = 0x50000; std::vector sections;