Merge pull request #4608 from MiniRML/fix/relinker-icon-png-chunks

fix(relinker): validate icon PNG critical chunks and order
This commit is contained in:
Adrià 2026-10-13 17:50:39 +02:00 committed by GitHub
commit 34cee1ef15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 84 additions and 1 deletions

View File

@ -30,6 +30,9 @@ void validatePng(const std::vector<std::uint8_t>& 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<std::uint8_t>& 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");

View File

@ -27,9 +27,76 @@ std::uint32_t readU32(const std::vector<std::uint8_t>& data, const std::size_t o
static_cast<std::uint32_t>(data[offset + 2]) << 16 | static_cast<std::uint32_t>(data[offset + 3]) << 24;
}
std::vector<std::uint8_t> chunk(const std::string& type, const std::vector<std::uint8_t>& payload) {
std::vector<std::uint8_t> result;
const auto append = [&result](std::uint32_t value) {
for (const auto shift : {24, 16, 8, 0}) result.push_back(static_cast<std::uint8_t>(value >> shift));
};
append(static_cast<std::uint32_t>(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<std::vector<std::uint8_t>>& chunks, bool valid, const std::vector<std::uint8_t>& header = {}) {
std::vector<std::uint8_t> 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<Elfpatcher::Windows::PeSection> 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<std::uint8_t> 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<std::uint8_t>(771)), idat}, false);
std::vector<std::uint8_t> 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<std::uint8_t>(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<Elfpatcher::Windows::PeSection> sections;