/* * Copyright (c) 2024, Sönke Holz * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include namespace Kernel { // This function only works on ELF addresses that are not in a zero-padded area of a program header. static ErrorOr get_data_at_kernel_elf_virtual_address(ELF::Image const& kernel_elf_image, Bytes kernel_elf_image_data, FlatPtr start_vaddr, size_t size) { ErrorOr result = EINVAL; static Optional cached_program_header; static auto is_range_in_program_header = [](FlatPtr start_vaddr, size_t size, ELF::Image::ProgramHeader const& program_header) { return start_vaddr >= program_header.vaddr().get() && start_vaddr + size <= program_header.vaddr().get() + program_header.size_in_memory(); }; if (cached_program_header.has_value() && is_range_in_program_header(start_vaddr, size, cached_program_header.value())) return kernel_elf_image_data.slice(cached_program_header->offset() + start_vaddr - cached_program_header->vaddr().get(), size); kernel_elf_image.for_each_program_header([&result, kernel_elf_image_data, start_vaddr, size](ELF::Image::ProgramHeader const& program_header) { if (program_header.type() == PT_LOAD && is_range_in_program_header(start_vaddr, size, program_header)) { result = kernel_elf_image_data.slice(program_header.offset() + start_vaddr - program_header.vaddr().get(), size); cached_program_header = program_header; return IterationDecision::Break; } return IterationDecision::Continue; }); return result; } void perform_kernel_relocations(ELF::Image const& kernel_elf_image, Bytes kernel_elf_image_data, FlatPtr base_address) { size_t dynamic_section_offset { 0 }; kernel_elf_image.for_each_program_header([&dynamic_section_offset](ELF::Image::ProgramHeader const& program_header) { if (program_header.type() == PT_DYNAMIC) { dynamic_section_offset = program_header.offset(); return IterationDecision::Break; } return IterationDecision::Continue; }); if (dynamic_section_offset == 0) PANIC("Kernel image does not have a PT_DYNAMIC program header; can't perform relocations"); FlatPtr relocation_table_vaddr = 0; size_t relocation_table_size = 0; size_t relocation_entry_size = 0; size_t number_of_relative_relocs = 0; FlatPtr relr_relocation_table_vaddr = 0; size_t relr_relocation_table_size = 0; auto const* dynamic_section_entries = bit_cast(bit_cast(kernel_elf_image_data.data()) + dynamic_section_offset); for (size_t i = 0;; i++) { auto const& entry = dynamic_section_entries[i]; if (entry.d_tag == DT_NULL) break; switch (entry.d_tag) { case DT_REL: case DT_RELSZ: case DT_RELENT: case DT_RELCOUNT: PANIC("DT_REL relocation tables are not supported"); case DT_RELA: relocation_table_vaddr = entry.d_un.d_ptr; break; case DT_RELASZ: relocation_table_size = entry.d_un.d_val; break; case DT_RELAENT: relocation_entry_size = entry.d_un.d_val; break; case DT_RELACOUNT: number_of_relative_relocs = entry.d_un.d_val; break; case DT_RELR: relr_relocation_table_vaddr = entry.d_un.d_ptr; break; case DT_RELRSZ: relr_relocation_table_size = entry.d_un.d_val; break; case DT_RELRENT: VERIFY(entry.d_un.d_val == sizeof(FlatPtr)); break; } } VERIFY((relocation_table_vaddr != 0 && relocation_table_size != 0 && relocation_entry_size != 0 && number_of_relative_relocs != 0) || (relr_relocation_table_vaddr != 0 && relr_relocation_table_size != 0)); if (relocation_table_vaddr != 0) { // We are still identity mapped, so translate the address of the relocation table. auto relocation_table_data = MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, relocation_table_vaddr, relocation_table_size)); for (size_t i = 0; i < number_of_relative_relocs; i++) { auto const& raw_relocation = *bit_cast(bit_cast(relocation_table_data.data()) + (i * relocation_entry_size)); ELF::Image::Relocation relocation(kernel_elf_image, raw_relocation, true); VERIFY(relocation.type() == to_underlying(ELF::GenericDynamicRelocationType::RELATIVE)); // Elf_Rela::r_offset is a virtual address for executables, so we need to translate it as well. auto* patch_ptr = bit_cast(MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, relocation.offset(), sizeof(FlatPtr))).data()); FlatPtr relocated_address = base_address + relocation.addend(); __builtin_memcpy(patch_ptr, &relocated_address, sizeof(FlatPtr)); } } if (relr_relocation_table_vaddr != 0) { auto patch_relr = [&kernel_elf_image, kernel_elf_image_data, base_address](FlatPtr patch_vaddr) { auto* patch_ptr = bit_cast(MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, patch_vaddr, sizeof(FlatPtr))).data()); FlatPtr relocated_address; __builtin_memcpy(&relocated_address, patch_ptr, sizeof(FlatPtr)); relocated_address += base_address; __builtin_memcpy(patch_ptr, &relocated_address, sizeof(FlatPtr)); }; auto relr_relocation_table_data = MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, relr_relocation_table_vaddr, relr_relocation_table_size)); auto* entries = bit_cast(relr_relocation_table_data.data()); FlatPtr patch_vaddr = 0; for (size_t i = 0; i < relr_relocation_table_size / sizeof(FlatPtr); i++) { if ((entries[i] & 1) == 0) { patch_vaddr = entries[i]; patch_relr(patch_vaddr); patch_vaddr += sizeof(FlatPtr); } else { auto bitmap = entries[i]; for (size_t j = 0; (bitmap >>= 1) != 0; j++) if ((bitmap & 1) != 0) patch_relr(patch_vaddr + (j * sizeof(FlatPtr))); patch_vaddr += (8 * sizeof(FlatPtr) - 1) * sizeof(FlatPtr); } } } } }