Merge pull request #2618 from oneandonlydean/fix/carry-2549

fix(relinker): leave zero-filled PT_LOAD memory out of Windows files
This commit is contained in:
Adrià 2026-10-10 10:38:10 +02:00 committed by GitHub
commit 05e884ca08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 4 deletions

View File

@ -253,6 +253,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_working_directory.py $<TARGET_FILE:relinker>)
add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $<TARGET_FILE:relinker>)
add_test(NAME windows_bss COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_bss.py $<TARGET_FILE:relinker>)
add_test(NAME windows_icon COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_icon.py $<TARGET_FILE:relinker>)
add_test(NAME guest_intel_trampolines COMMAND ${Python3_EXECUTABLE}

View File

@ -44,8 +44,9 @@ std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& s
const auto& section = sections[index];
if (section.Name.empty() || section.Name.size() > 8 || section.Data.empty() || section.Rva != endRva)
throw Domain::RelinkerException("Invalid PE section layout: " + section.Name, section.Rva);
const auto rawSize = Io::AlignUp(CheckedRva(section.Data.size()), FileAlignment);
const auto rawOffset = CheckedRva(result.size());
const auto initialized = static_cast<std::size_t>(std::find_if(section.Data.rbegin(), section.Data.rend(), [](const std::uint8_t value) { return value != 0; }).base() - section.Data.begin());
const auto rawSize = Io::AlignUp(CheckedRva(initialized), FileAlignment);
const auto rawOffset = rawSize == 0 ? 0u : CheckedRva(result.size());
const auto header = sectionTable + index * 40;
std::copy(section.Name.begin(), section.Name.end(), result.begin() + static_cast<std::ptrdiff_t>(header));
Io::WriteU32(result, header + 8, CheckedRva(section.Data.size()));
@ -61,8 +62,10 @@ std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& s
} else {
dataSize = CheckedRva(static_cast<std::uint64_t>(dataSize) + rawSize);
}
result.insert(result.end(), section.Data.begin(), section.Data.end());
result.resize(static_cast<std::size_t>(rawOffset) + rawSize);
if (rawSize != 0) {
result.insert(result.end(), section.Data.begin(), section.Data.begin() + static_cast<std::ptrdiff_t>(initialized));
result.resize(static_cast<std::size_t>(rawOffset) + rawSize);
}
endRva = AlignRva(section.Rva + static_cast<std::uint64_t>(section.Data.size()));
}
if (!foundEntry)

View File

@ -0,0 +1,72 @@
"""Leave the zero-filled tail of PT_LOAD memory out of the Windows file and let the loader zero-fill it."""
import os
from pathlib import Path
import struct
import subprocess
import sys
import tempfile
from test_optional_plt import fixture
BSS = 64 << 20
FILE_ALIGNMENT = 0x200
SECTION_ALIGNMENT = 0x1000
LOAD_RVA = 0x10000
def sections(pe):
header = struct.unpack_from("<I", pe, 0x3c)[0]
count = struct.unpack_from("<H", pe, header + 6)[0]
optional = header + 24
table = optional + struct.unpack_from("<H", pe, header + 20)[0]
result = []
for index in range(count):
offset = table + index * 40
name = pe[offset:offset + 8].rstrip(b"\0").decode()
virtual_size, rva, raw_size, raw = struct.unpack_from("<IIII", pe, offset + 8)
result.append((name, virtual_size, rva, raw_size, raw))
return result, struct.unpack_from("<I", pe, optional + 56)[0]
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-windows-bss-") as directory:
work = Path(directory)
image = fixture()
memory_size = struct.unpack_from("<Q", image, 64 + 40)[0] + BSS
struct.pack_into("<Q", image, 64 + 40, memory_size)
last = memory_size - 4
image[0x210:0x21a] = b"\x8b\x05" + struct.pack("<i", last - 0x216) + b"\x83\xc0\x2a\xc3"
source = work / "input.elf"
source.write_bytes(image)
output = work / "output.exe"
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)],
capture_output=True, text=True, timeout=20)
assert result.returncode == 0, (result.stdout, result.stderr)
pe = output.read_bytes()
assert len(pe) < BSS // 16, f"{len(pe)} byte executable for {BSS} bytes of zero-filled memory"
table, image_size = sections(pe)
first = next(section for section in table if section[2] == LOAD_RVA)
name, virtual_size, rva, raw_size, raw = first
assert virtual_size >= memory_size and rva + virtual_size <= image_size, first
assert 0 < raw_size < SECTION_ALIGNMENT * 2, first
for name, virtual_size, rva, raw_size, raw in table:
assert raw_size % FILE_ALIGNMENT == 0 and raw % FILE_ALIGNMENT == 0, name
assert raw_size <= -(-virtual_size // FILE_ALIGNMENT) * FILE_ALIGNMENT, name
assert (raw_size == 0) == (raw == 0) and raw + raw_size <= len(pe), name
mapped = bytearray(image_size)
for name, virtual_size, rva, raw_size, raw in table:
size = min(raw_size, virtual_size)
mapped[rva:rva + size] = pe[raw:raw + size]
loaded = mapped[LOAD_RVA:LOAD_RVA + memory_size]
assert loaded[0x210:0x21a] == image[0x210:0x21a]
assert not any(loaded[0x1000:])
if os.name == "nt":
executed = subprocess.run([str(output)], capture_output=True, text=True, timeout=20)
assert executed.returncode == 42, f"exit code {executed.returncode:#x}"
print("Windows zero-filled memory tests passed")
if __name__ == "__main__":
main()