Merge pull request #4619 from MiniRML/fix/nid-version-requirement-chains
fix(nid): bound version requirement chains to their section
This commit is contained in:
commit
6629c09392
|
|
@ -214,6 +214,10 @@ if(BUILD_TESTING)
|
|||
target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libkernel libc)
|
||||
configure_windows_unwind(guest_kernel_sys_tests)
|
||||
add_test(NAME guest_kernel_sys COMMAND guest_kernel_sys_tests)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
add_test(NAME nid_verneed COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_verneed.py $<TARGET_FILE:nid_patcher>)
|
||||
endif()
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_kernel_sys PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
|
||||
|
|
|
|||
|
|
@ -216,49 +216,65 @@ void RemapDynamicNeededOffsets(
|
|||
}
|
||||
}
|
||||
|
||||
struct VersionNameField {
|
||||
std::size_t Offset;
|
||||
std::uint32_t Name;
|
||||
};
|
||||
|
||||
std::vector<VersionNameField> ReadVersionNameFields(const std::vector<std::uint8_t>& elf, const Elf64_Ehdr& ehdr) {
|
||||
using namespace Internal;
|
||||
std::vector<VersionNameField> fields;
|
||||
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
|
||||
const auto section = Read<Elf64_Shdr>(elf, static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr));
|
||||
if (section.sh_type != kShtGnuVerneed || section.sh_size == 0u) continue;
|
||||
if (section.sh_offset > elf.size() || section.sh_size > elf.size() - section.sh_offset)
|
||||
throw std::runtime_error("invalid .gnu.version_r section range");
|
||||
const auto location = [&](std::uint64_t relative, std::size_t size) {
|
||||
if (relative > section.sh_size || size > section.sh_size - relative)
|
||||
throw std::runtime_error(".gnu.version_r record outside section");
|
||||
return static_cast<std::size_t>(section.sh_offset + relative);
|
||||
};
|
||||
const auto advance = [&](std::uint64_t current, std::uint32_t step, std::size_t size) {
|
||||
if (step < size || step > section.sh_size - current)
|
||||
throw std::runtime_error("invalid .gnu.version_r record link");
|
||||
return current + step;
|
||||
};
|
||||
std::uint64_t current = 0u;
|
||||
while (true) {
|
||||
const auto offset = location(current, sizeof(Elf64_Verneed));
|
||||
const auto need = Read<Elf64_Verneed>(elf, offset);
|
||||
if (need.vn_version != 1u) throw std::runtime_error("unsupported .gnu.version_r structure version");
|
||||
fields.push_back({offset + offsetof(Elf64_Verneed, vn_file), need.vn_file});
|
||||
if (need.vn_cnt != 0u) {
|
||||
auto auxiliary = advance(current, need.vn_aux, sizeof(Elf64_Verneed));
|
||||
for (std::uint16_t index = 0u; index < need.vn_cnt; ++index) {
|
||||
const auto auxOffset = location(auxiliary, sizeof(Elf64_Vernaux));
|
||||
const auto aux = Read<Elf64_Vernaux>(elf, auxOffset);
|
||||
fields.push_back({auxOffset + offsetof(Elf64_Vernaux, vna_name), aux.vna_name});
|
||||
if (index + 1u == need.vn_cnt) {
|
||||
if (aux.vna_next != 0u) throw std::runtime_error(".gnu.version_r auxiliary count mismatch");
|
||||
} else {
|
||||
auxiliary = advance(auxiliary, aux.vna_next, sizeof(Elf64_Vernaux));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (need.vn_next == 0u) break;
|
||||
current = advance(current, need.vn_next, sizeof(Elf64_Verneed));
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
void RemapVerneedOffsets(
|
||||
std::vector<std::uint8_t>& elf,
|
||||
const Elf64_Ehdr& ehdr,
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>>& oldToNewOffset
|
||||
) {
|
||||
using namespace Internal;
|
||||
|
||||
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
|
||||
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
|
||||
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
|
||||
if (shdr.sh_type != kShtGnuVerneed) continue;
|
||||
|
||||
const std::size_t verneedOffset = static_cast<std::size_t>(shdr.sh_offset);
|
||||
std::size_t entryOffset = verneedOffset;
|
||||
|
||||
while (true) {
|
||||
const auto verneed = Read<Elf64_Verneed>(elf, entryOffset);
|
||||
|
||||
const auto mappedFile = std::find_if(
|
||||
oldToNewOffset.begin(), oldToNewOffset.end(),
|
||||
[&](const auto& entry) { return entry.first == verneed.vn_file; }
|
||||
);
|
||||
if (mappedFile != oldToNewOffset.end())
|
||||
Write(elf, entryOffset + offsetof(Elf64_Verneed, vn_file), mappedFile->second);
|
||||
|
||||
std::size_t auxOffset = entryOffset + verneed.vn_aux;
|
||||
for (std::uint16_t a = 0u; a < verneed.vn_cnt; ++a) {
|
||||
const auto vernaux = Read<Elf64_Vernaux>(elf, auxOffset);
|
||||
|
||||
const auto mappedName = std::find_if(
|
||||
oldToNewOffset.begin(), oldToNewOffset.end(),
|
||||
[&](const auto& entry) { return entry.first == vernaux.vna_name; }
|
||||
);
|
||||
if (mappedName != oldToNewOffset.end())
|
||||
Write(elf, auxOffset + offsetof(Elf64_Vernaux, vna_name), mappedName->second);
|
||||
|
||||
if (vernaux.vna_next == 0u) break;
|
||||
auxOffset += vernaux.vna_next;
|
||||
}
|
||||
|
||||
if (verneed.vn_next == 0u) break;
|
||||
entryOffset += verneed.vn_next;
|
||||
}
|
||||
for (const auto& field : ReadVersionNameFields(elf, ehdr)) {
|
||||
const auto mapped = std::find_if(oldToNewOffset.begin(), oldToNewOffset.end(),
|
||||
[&](const auto& entry) { return entry.first == field.Name; });
|
||||
if (mapped != oldToNewOffset.end()) Write(elf, field.Offset, mapped->second);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,35 +303,8 @@ std::vector<std::uint32_t> CollectDynamicNameOffsets(const std::vector<std::uint
|
|||
}
|
||||
|
||||
std::vector<std::uint32_t> CollectVerneedNameOffsets(const std::vector<std::uint8_t>& elf, const Elf64_Ehdr& ehdr) {
|
||||
using namespace Internal;
|
||||
|
||||
std::vector<std::uint32_t> offsets;
|
||||
|
||||
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
|
||||
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
|
||||
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
|
||||
if (shdr.sh_type != kShtGnuVerneed) continue;
|
||||
|
||||
std::size_t entryOffset = static_cast<std::size_t>(shdr.sh_offset);
|
||||
|
||||
while (true) {
|
||||
const auto verneed = Read<Elf64_Verneed>(elf, entryOffset);
|
||||
offsets.push_back(verneed.vn_file);
|
||||
|
||||
std::size_t auxOffset = entryOffset + verneed.vn_aux;
|
||||
for (std::uint16_t a = 0u; a < verneed.vn_cnt; ++a) {
|
||||
const auto vernaux = Read<Elf64_Vernaux>(elf, auxOffset);
|
||||
offsets.push_back(vernaux.vna_name);
|
||||
|
||||
if (vernaux.vna_next == 0u) break;
|
||||
auxOffset += vernaux.vna_next;
|
||||
}
|
||||
|
||||
if (verneed.vn_next == 0u) break;
|
||||
entryOffset += verneed.vn_next;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& field : ReadVersionNameFields(elf, ehdr)) offsets.push_back(field.Name);
|
||||
return offsets;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
import struct
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from test_symbol_versions import build_image, symbols, patch, SHOFF, DYNSTR_OFFSET, sections
|
||||
|
||||
VERNEED = 0x580
|
||||
|
||||
|
||||
def image(*, auxiliary=16, count=1, size=32, version=1, auxnext=0, nextrecord=0):
|
||||
data = build_image(symbols(False), False)
|
||||
strings = sections(data)[2]
|
||||
filename = strings[5]
|
||||
names = b"libfixture.so\0FIXTURE_1\0"
|
||||
data[DYNSTR_OFFSET + filename:DYNSTR_OFFSET + filename + len(names)] = names
|
||||
struct.pack_into("<Q", data, SHOFF + 2 * 64 + 32, filename + len(names))
|
||||
struct.pack_into("<H", data, 0x3c, 5)
|
||||
struct.pack_into("<IIQQQQIIQQ", data, SHOFF + 4 * 64, 0, 0x6ffffffe, 2, 0, VERNEED, size, 2, 1, 8, 0)
|
||||
struct.pack_into("<HHIII", data, VERNEED, version, count, filename, auxiliary, nextrecord)
|
||||
struct.pack_into("<IHHII", data, VERNEED + auxiliary, 0, 0, 2, filename + len(b"libfixture.so\0"), auxnext)
|
||||
return data
|
||||
|
||||
|
||||
class VersionRequirementsTests(unittest.TestCase):
|
||||
def test_auxiliary_record_cannot_leave_declared_section(self):
|
||||
self.reject(image(auxiliary=32))
|
||||
|
||||
def test_auxiliary_count_must_match_chain(self):
|
||||
self.reject(image(count=2))
|
||||
|
||||
def test_unsupported_structure_version_is_rejected(self):
|
||||
self.reject(image(version=2))
|
||||
|
||||
def test_invalid_forward_links_and_section_ranges_are_rejected(self):
|
||||
for fields in ({"auxnext": 16}, {"nextrecord": 64}, {"size": 15},
|
||||
{"size": 0x10000}, {"auxiliary": 8}, {"count": 2, "auxnext": 8}):
|
||||
with self.subTest(fields=fields):
|
||||
self.reject(image(**fields))
|
||||
|
||||
def test_valid_multiple_auxiliaries_and_requirements(self):
|
||||
data = image(count=2, size=80, auxnext=16, nextrecord=48)
|
||||
original_name = struct.unpack_from("<I", data, VERNEED + 24)[0]
|
||||
original_file = struct.unpack_from("<I", data, VERNEED + 4)[0]
|
||||
struct.pack_into("<IHHII", data, VERNEED + 32, 0, 0, 3, original_name, 0)
|
||||
struct.pack_into("<HHIII", data, VERNEED + 48, 1, 1, original_file, 16, 0)
|
||||
struct.pack_into("<IHHII", data, VERNEED + 64, 0, 0, 4, original_name, 0)
|
||||
result, after = patch(PATCHER, bytes(data), "multiple-versions.so")
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
strings = sections(after)[2]
|
||||
for position, expected in ((4, b"libfixture.so"), (24, b"FIXTURE_1"),
|
||||
(40, b"FIXTURE_1"), (52, b"libfixture.so"), (72, b"FIXTURE_1")):
|
||||
offset = struct.unpack_from("<I", after, VERNEED + position)[0]
|
||||
start = strings[4] + offset
|
||||
self.assertEqual(after[start:after.index(0, start)], expected)
|
||||
|
||||
def reject(self, data):
|
||||
result, after = patch(PATCHER, bytes(data), "versions.so")
|
||||
self.assertEqual(result.returncode, 2, (result.returncode, result.stderr))
|
||||
self.assertIn(b"version_r", result.stderr)
|
||||
self.assertEqual(after, data)
|
||||
|
||||
def test_valid_names_are_preserved_when_strings_are_rebuilt(self):
|
||||
data = image()
|
||||
result, after = patch(PATCHER, bytes(data), "valid-versions.so")
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
strings = sections(after)[2]
|
||||
for position, expected in ((VERNEED + 4, b"libfixture.so"), (VERNEED + 16 + 8, b"FIXTURE_1")):
|
||||
offset = struct.unpack_from("<I", after, position)[0]
|
||||
start = strings[4] + offset
|
||||
self.assertEqual(after[start:after.index(0, start)], expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
PATCHER = Path(sys.argv.pop(1)).resolve()
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue