Merge pull request #29363 from medhefgo/elf2efi

elf2efi: Rework to allow using any linker
This commit is contained in:
Luca Boccassi 2023-09-29 23:01:53 +01:00 committed by GitHub
commit 69f99d1e4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 259 additions and 193 deletions

View file

@ -45,7 +45,7 @@ PACKAGES=(
libxkbcommon-dev
libxtables-dev
libzstd-dev
mold
# mold
mount
net-tools
python3-evdev
@ -68,6 +68,14 @@ LINKER="${LINKER:?}"
CRYPTOLIB="${CRYPTOLIB:?}"
RELEASE="$(lsb_release -cs)"
# mold-2.2.0+ fixes some bugs breaking bootloader builds.
# TODO: Switch to distro mold with ubuntu-24.04
if [[ "$LINKER" == mold ]]; then
wget https://github.com/rui314/mold/releases/download/v2.2.0/mold-2.2.0-x86_64-linux.tar.gz
echo "d66e0230c562c2ba0e0b789cc5034e0fa2369cc843d0154920de4269cd94afeb mold-2.2.0-x86_64-linux.tar.gz" | sha256sum -c
sudo tar -xz -C /usr --strip-components=1 -f mold-2.2.0-x86_64-linux.tar.gz
fi
# Note: As we use postfixed clang/gcc binaries, we need to override $AR
# as well, otherwise meson falls back to ar from binutils which
# doesn't work with LTO
@ -123,20 +131,11 @@ ninja --version
for args in "${ARGS[@]}"; do
SECONDS=0
# mold < 1.1 does not support LTO.
if dpkg --compare-versions "$(dpkg-query --showformat='${Version}' --show mold)" ge 1.1; then
fatal "Newer mold version detected, please remove this workaround."
elif [[ "$args" == *"-Db_lto=true"* ]]; then
LD="gold"
else
LD="$LINKER"
fi
info "Checking build with $args"
# shellcheck disable=SC2086
if ! AR="$AR" \
CC="$CC" CC_LD="$LD" CFLAGS="-Werror" \
CXX="$CXX" CXX_LD="$LD" CXXFLAGS="-Werror" \
CC="$CC" CC_LD="$LINKER" CFLAGS="-Werror" \
CXX="$CXX" CXX_LD="$LINKER" CXXFLAGS="-Werror" \
meson setup \
-Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true --werror \
-Dnobody-group=nogroup -Dcryptolib="${CRYPTOLIB:?}" -Ddebug=false \

View file

@ -26,10 +26,10 @@ jobs:
matrix:
env:
- { COMPILER: "gcc", COMPILER_VERSION: "11", LINKER: "bfd", CRYPTOLIB: "gcrypt" }
- { COMPILER: "gcc", COMPILER_VERSION: "13", LINKER: "gold", CRYPTOLIB: "openssl" }
- { COMPILER: "clang", COMPILER_VERSION: "14", LINKER: "mold", CRYPTOLIB: "gcrypt" }
- { COMPILER: "clang", COMPILER_VERSION: "15", LINKER: "lld", CRYPTOLIB: "openssl" }
- { COMPILER: "clang", COMPILER_VERSION: "16", LINKER: "bfd", CRYPTOLIB: "auto" }
- { COMPILER: "gcc", COMPILER_VERSION: "13", LINKER: "mold", CRYPTOLIB: "openssl" }
- { COMPILER: "clang", COMPILER_VERSION: "14", LINKER: "bfd", CRYPTOLIB: "gcrypt" }
- { COMPILER: "clang", COMPILER_VERSION: "15", LINKER: "mold", CRYPTOLIB: "openssl" }
- { COMPILER: "clang", COMPILER_VERSION: "17", LINKER: "lld", CRYPTOLIB: "auto" }
env: ${{ matrix.env }}
steps:
- name: Repository checkout

View file

@ -1788,7 +1788,6 @@ conf.set10('ENABLE_UKIFY', want_ukify)
############################################################
elf2efi_lds = project_source_root / 'tools/elf2efi.lds'
elf2efi_py = find_program('tools/elf2efi.py')
export_dbus_interfaces_py = find_program('tools/dbus_exporter.py')
generate_gperfs = find_program('tools/generate-gperfs.py')

View file

@ -4,12 +4,11 @@
#include "version.h"
/* Magic string for recognizing our own binaries */
_used_ _section_(".sdmagic") static const char magic[] =
"#### LoaderInfo: systemd-addon " GIT_VERSION " ####";
DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-addon " GIT_VERSION " ####");
/* This is intended to carry data, not to be executed */
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table);
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) {
return EFI_UNSUPPORTED;
return EFI_UNSUPPORTED;
}

View file

@ -26,14 +26,15 @@
#include "vmm.h"
/* Magic string for recognizing our own binaries */
_used_ _section_(".sdmagic") static const char magic[] =
"#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
#define SD_MAGIC "#### LoaderInfo: systemd-boot " GIT_VERSION " ####"
DECLARE_NOALLOC_SECTION(".sdmagic", SD_MAGIC);
/* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
_used_ _section_(".osrel") static const char osrel[] =
"ID=systemd-boot\n"
"VERSION=\"" GIT_VERSION "\"\n"
"NAME=\"systemd-boot " GIT_VERSION "\"\n";
DECLARE_NOALLOC_SECTION(
".osrel",
"ID=systemd-boot\n"
"VERSION=\"" GIT_VERSION "\"\n"
"NAME=\"systemd-boot " GIT_VERSION "\"\n");
DECLARE_SBAT(SBAT_BOOT_SECTION_TEXT);
@ -1890,14 +1891,14 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) {
assert(loader_path);
err = pe_file_locate_sections(root_dir, loader_path, sections, &offset, &size);
if (err != EFI_SUCCESS || size != sizeof(magic))
if (err != EFI_SUCCESS || size != sizeof(SD_MAGIC))
return false;
err = file_read(root_dir, loader_path, offset, size, &content, &read);
if (err != EFI_SUCCESS || size != read)
return false;
return memcmp(content, magic, sizeof(magic)) == 0;
return memcmp(content, SD_MAGIC, sizeof(SD_MAGIC)) == 0;
}
static ConfigEntry *config_entry_add_loader_auto(

View file

@ -81,7 +81,7 @@ void __stack_chk_guard_init(void) {
(void) rng->GetRNG(rng, NULL, sizeof(__stack_chk_guard), (void *) &__stack_chk_guard);
else
/* Better than no extra entropy. */
__stack_chk_guard ^= (intptr_t) &__ImageBase;
__stack_chk_guard ^= (intptr_t) __executable_start;
}
#endif

View file

@ -147,10 +147,6 @@ if get_option('mode') == 'developer' and get_option('debug')
endif
efi_c_ld_args = [
# We only support bfd. gold is going away, lld has issues with LTO on x86
# and mold does not support linker scripts.
'-fuse-ld=bfd',
'-lgcc',
'-nostdlib',
'-static-pie',
@ -166,13 +162,14 @@ efi_c_ld_args = [
'-z', 'max-page-size=4096',
'-z', 'noexecstack',
'-z', 'norelro',
'-T' + elf2efi_lds,
'-z', 'relro',
'-z', 'separate-code',
]
# On CentOS 8 the nopack-relative-relocs linker flag is not supported, and we get:
# /usr/bin/ld.bfd: warning: -z nopack-relative-relocs ignored
efi_c_ld_args += cc.get_supported_link_arguments('-Wl,-z,nopack-relative-relocs')
efi_c_ld_args += cc.get_supported_link_arguments(
# binutils >= 2.38
'-Wl,-z,nopack-relative-relocs',
)
# efi_c_args is explicitly passed to targets so that they can override distro-provided flags
# that should not be used for EFI binaries.
@ -213,11 +210,35 @@ efi_arch_c_args = {
efi_arch_c_ld_args = {
# libgcc is not compiled with -fshort-wchar, but it does not use it anyways,
# so it's fine to link against it.
'arm' : ['-Wl,--no-wchar-size-warning'],
'arm' : cc.get_supported_link_arguments('-Wl,--no-wchar-size-warning'),
'x86_64' : ['-m64'],
'x86' : ['-m32'],
}
linker_sanity_code = 'void a(void) {}; void _start(void) { a(); }'
linker_sanity_args = ['-nostdlib', '-Wl,--fatal-warnings']
if not cc.links(linker_sanity_code,
name : 'linker supports -static-pie',
args : [linker_sanity_args, '-static-pie'])
error('Linker does not support -static-pie.')
endif
# https://github.com/llvm/llvm-project/issues/67152
if not cc.links(linker_sanity_code,
name : 'linker supports LTO with -nostdlib',
args : [linker_sanity_args, '-flto'])
efi_c_args += '-fno-lto'
efi_c_ld_args += '-fno-lto'
endif
# https://github.com/llvm/llvm-project/issues/61101
if efi_cpu_family_alt == 'x86' and not cc.links(linker_sanity_code,
name : 'linker supports LTO with -nostdlib (x86)',
args : [linker_sanity_args, '-flto', '-m32'])
efi_arch_c_args += { 'x86' : efi_arch_c_args['x86'] + '-fno-lto' }
efi_arch_c_ld_args += { 'x86' : efi_arch_c_ld_args['x86'] + '-fno-lto' }
endif
############################################################
libefi_sources = files(
@ -315,8 +336,6 @@ foreach archspec : efi_archspecs
'include_directories' : efi_includes,
'c_args' : archspec['c_args'],
'link_args' : archspec['link_args'],
'link_with' : libefi,
'link_depends' : elf2efi_lds,
'gnu_symbol_visibility' : 'hidden',
'override_options' : efi_override_options,
'pie' : true,
@ -325,12 +344,14 @@ foreach archspec : efi_archspecs
efi_elf_binaries += executable(
'systemd-boot' + archspec['arch'],
sources : [systemd_boot_sources, version_h],
link_with : libefi,
name_suffix : 'elf',
kwargs : kwargs)
efi_elf_binaries += executable(
'linux' + archspec['arch'],
sources : [stub_sources, version_h],
link_with : libefi,
name_suffix : 'elf.stub',
kwargs : kwargs)
@ -362,6 +383,7 @@ foreach efi_elf_binary : efi_elf_binaries
'--efi-minor=1',
'--subsystem=10',
'--minimum-sections=' + minimum_sections,
'--copy-sections=.sbat,.sdmagic,.osrel',
'@INPUT@',
'@OUTPUT@',
])

View file

@ -21,7 +21,7 @@
#include "vmm.h"
/* magic string to find in the binary image */
_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####");
DECLARE_SBAT(SBAT_STUB_SECTION_TEXT);

View file

@ -555,7 +555,7 @@ uint64_t get_os_indications_supported(void) {
__attribute__((noinline)) void notify_debugger(const char *identity, volatile bool wait) {
#ifdef EFI_DEBUG
printf("%s@%p %s\n", identity, &__ImageBase, GIT_VERSION);
printf("%s@%p %s\n", identity, __executable_start, GIT_VERSION);
if (wait)
printf("Waiting for debugger to attach...\n");

View file

@ -6,8 +6,8 @@
#include "proto/file-io.h"
#include "string-util-fundamental.h"
/* This is provided by linker script. */
extern uint8_t __ImageBase;
/* This is provided by the linker. */
extern uint8_t __executable_start[];
static inline void free(void *p) {
if (!p)

View file

@ -396,9 +396,15 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
type name[]; \
}
/* Declares an ELF read-only string section that does not occupy memory at runtime. */
#define DECLARE_NOALLOC_SECTION(name, text) \
asm(".pushsection " name ",\"S\"\n\t" \
".ascii " STRINGIFY(text) "\n\t" \
".zero 1\n\t" \
".popsection\n")
#ifdef SBAT_DISTRO
#define DECLARE_SBAT(text) \
static const char sbat[] _used_ _section_(".sbat") = (text)
#define DECLARE_SBAT(text) DECLARE_NOALLOC_SECTION(".sbat", text)
#else
#define DECLARE_SBAT(text)
#endif

View file

@ -1,58 +0,0 @@
SECTIONS {
__ImageBase = .;
/* We skip the first page because the space will be occupied by the PE headers after conversion. */
. = CONSTANT(MAXPAGESIZE);
.text ALIGN(CONSTANT(MAXPAGESIZE)) : {
*(.text .text.*)
}
/* When linking a minimal addon stub, the linker can merge .text and .dynsym, creating a RWE
* segment, and then rejects it. Ensure there's a gap so that we end up with two separate segments.
* The alignments for the next sections are only applied if the section exists, so they are not
* enough, and we need to have this unconditional one. */
. = ALIGN(CONSTANT(MAXPAGESIZE));
.rodata ALIGN(CONSTANT(MAXPAGESIZE)) : {
*(.rodata .rodata.*)
*(.srodata .srodata.*)
}
.data ALIGN(CONSTANT(MAXPAGESIZE)) : {
*(.data .data.*)
*(.sdata .sdata.*)
*(.got .got.*)
*(.got.plt .got.plt.*)
/* EDK2 says some firmware cannot handle BSS sections properly. */
*(.bss .bss.*)
*(.sbss .sbss.*)
*(COMMON)
}
.sdmagic ALIGN(CONSTANT(MAXPAGESIZE)) : { *(.sdmagic) }
.osrel ALIGN(CONSTANT(MAXPAGESIZE)) : { *(.osrel) }
.sbat ALIGN(CONSTANT(MAXPAGESIZE)) : { *(.sbat) }
/* These are used for PE conversion and then discarded. */
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.dynamic : { *(.dynamic) }
.rel.dyn : { *(.rel.dyn) }
.rela.dyn : { *(.rela.dyn) }
/* These aren't needed and could be discarded. Just in case that they're useful to the debugger
* we keep them, but move them out of the way to keep the PE binary more compact. */
.ARM.exidx : { *(.ARM.exidx) }
.eh_frame : { *(.eh_frame) }
.eh_frame_hdr : { *(.eh_frame_hdr) }
.gnu.hash : { *(.gnu.hash) }
.hash : { *(.hash) }
.note.gnu.build-id : { *(.note.gnu.build-id ) }
/DISCARD/ : {
*(.ARM.attributes)
*(.comment)
*(.note.*)
*(.riscv.attributes)
}
}

View file

@ -39,7 +39,7 @@ from ctypes import (
)
from elftools.elf.constants import SH_FLAGS
from elftools.elf.elffile import ELFFile, Section as ELFSection
from elftools.elf.elffile import ELFFile
from elftools.elf.enums import (
ENUM_DT_FLAGS_1,
ENUM_RELOC_TYPE_AARCH64,
@ -204,6 +204,30 @@ assert sizeof(PeCoffHeader) == 20
assert sizeof(PeOptionalHeader32) == 224
assert sizeof(PeOptionalHeader32Plus) == 240
PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
IGNORE_SECTIONS = [
".eh_frame",
".eh_frame_hdr",
".ARM.exidx",
]
IGNORE_SECTION_TYPES = [
"SHT_DYNAMIC",
"SHT_DYNSYM",
"SHT_GNU_ATTRIBUTES",
"SHT_GNU_HASH",
"SHT_HASH",
"SHT_NOTE",
"SHT_REL",
"SHT_RELA",
"SHT_RELR",
"SHT_STRTAB",
"SHT_SYMTAB",
]
# EFI mandates 4KiB memory pages.
SECTION_ALIGNMENT = 4096
FILE_ALIGNMENT = 512
@ -217,79 +241,95 @@ def align_to(x: int, align: int) -> int:
return (x + align - 1) & ~(align - 1)
def use_section(elf_s: ELFSection) -> bool:
# These sections are either needed during conversion to PE or are otherwise not needed
# in the final PE image.
IGNORE_SECTIONS = [
".ARM.exidx",
".dynamic",
".dynstr",
".dynsym",
".eh_frame_hdr",
".eh_frame",
".gnu.hash",
".hash",
".note.gnu.build-id",
".rel.dyn",
".rela.dyn",
]
# Known sections we care about and want to be in the final PE.
COPY_SECTIONS = [
".data",
".osrel",
".rodata",
".sbat",
".sdmagic",
".text",
]
# By only dealing with allocating sections we effectively filter out debug sections.
if not elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC:
return False
if elf_s.name in IGNORE_SECTIONS:
return False
# For paranoia we only handle sections we know of. Any new sections that come up should
# be added to IGNORE_SECTIONS/COPY_SECTIONS and/or the linker script.
if elf_s.name not in COPY_SECTIONS:
raise RuntimeError(f"Unknown section {elf_s.name}, refusing.")
if elf_s["sh_addr"] % SECTION_ALIGNMENT != 0:
raise RuntimeError(f"Section {elf_s.name} is not aligned.")
if len(elf_s.name) > 8:
raise RuntimeError(f"ELF section name {elf_s.name} too long.")
return True
def align_down(x: int, align: int) -> int:
return x & ~(align - 1)
def convert_elf_section(elf_s: ELFSection) -> PeSection:
pe_s = PeSection()
pe_s.Name = elf_s.name.encode()
pe_s.VirtualSize = elf_s.data_size
pe_s.VirtualAddress = elf_s["sh_addr"]
pe_s.SizeOfRawData = align_to(elf_s.data_size, FILE_ALIGNMENT)
pe_s.data = bytearray(elf_s.data())
if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
pe_s.Characteristics = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE:
pe_s.Characteristics = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
else:
pe_s.Characteristics = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
return pe_s
def next_section_address(sections: typing.List[PeSection]) -> int:
return align_to(
sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
)
def copy_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
sections = []
def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
pe_s = None
# This is essentially the same as copying by ELF load segments, except that we assemble them
# manually, so that we can easily strip unwanted sections. We try to only discard things we know
# about so that there are no surprises.
relro = None
for elf_seg in elf.iter_segments():
if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT:
raise RuntimeError("ELF segments are not properly aligned.")
elif elf_seg["p_type"] == "PT_GNU_RELRO":
relro = elf_seg
for elf_s in elf.iter_sections():
if not use_section(elf_s):
if (
elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC == 0
or elf_s["sh_type"] in IGNORE_SECTION_TYPES
or elf_s.name in IGNORE_SECTIONS
):
continue
if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]:
raise RuntimeError(f"Unknown section {elf_s.name}.")
if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
rwx = PE_CHARACTERISTICS_RX
elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE:
rwx = PE_CHARACTERISTICS_RW
else:
rwx = PE_CHARACTERISTICS_R
# PE images are always relro.
if relro and relro.section_in_segment(elf_s):
rwx = PE_CHARACTERISTICS_R
if pe_s and pe_s.Characteristics != rwx:
yield pe_s
pe_s = None
if pe_s:
# Insert padding to properly align the section.
pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data)
pe_s.data += bytearray(pad_len) + elf_s.data()
else:
pe_s = PeSection()
pe_s.VirtualAddress = elf_s["sh_addr"]
pe_s.Characteristics = rwx
pe_s.data = elf_s.data()
if pe_s:
yield pe_s
def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
last_vma = 0
sections = []
for pe_s in iter_copy_sections(elf):
# Truncate the VMA to the nearest page and insert appropriate padding. This should not
# cause any overlap as this is pretty much how ELF *segments* are loaded/mmapped anyways.
# The ELF sections inside should also be properly aligned as we reuse the ELF VMA layout
# for the PE image.
vma = pe_s.VirtualAddress
pe_s.VirtualAddress = align_down(vma, SECTION_ALIGNMENT)
pe_s.data = bytearray(vma - pe_s.VirtualAddress) + pe_s.data
pe_s.VirtualSize = len(pe_s.data)
pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT)
pe_s.Name = {
PE_CHARACTERISTICS_RX: b".text",
PE_CHARACTERISTICS_RW: b".data",
PE_CHARACTERISTICS_R: b".rodata",
}[pe_s.Characteristics]
# This can happen if not building with `-z separate-code`.
if pe_s.VirtualAddress < last_vma:
raise RuntimeError("Overlapping PE sections.")
last_vma = pe_s.VirtualAddress + pe_s.VirtualSize
pe_s = convert_elf_section(elf_s)
if pe_s.Name == b".text":
opt.BaseOfCode = pe_s.VirtualAddress
opt.SizeOfCode += pe_s.VirtualSize
@ -304,6 +344,32 @@ def copy_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]
return sections
def copy_sections(
elf: ELFFile,
opt: PeOptionalHeader,
input_names: str,
sections: typing.List[PeSection],
):
for name in input_names.split(","):
elf_s = elf.get_section_by_name(name)
if not elf_s:
continue
if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0:
raise RuntimeError(f"ELF section {name} is not aligned.")
if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0:
raise RuntimeError(f"ELF section {name} is not read-only data.")
pe_s = PeSection()
pe_s.Name = name.encode()
pe_s.data = elf_s.data()
pe_s.VirtualAddress = next_section_address(sections)
pe_s.VirtualSize = len(elf_s.data())
pe_s.SizeOfRawData = align_to(len(elf_s.data()), FILE_ALIGNMENT)
pe_s.Characteristics = PE_CHARACTERISTICS_R
opt.SizeOfInitializedData += pe_s.VirtualSize
sections.append(pe_s)
def apply_elf_relative_relocation(
reloc: ElfRelocation,
image_base: int,
@ -325,7 +391,6 @@ def apply_elf_relative_relocation(
addend = target.data[addend_offset : addend_offset + addend_size]
addend = int.from_bytes(addend, byteorder="little")
# This currently assumes that the ELF file has an image base of 0.
value = (image_base + addend).to_bytes(addend_size, byteorder="little")
target.data[addend_offset : addend_offset + addend_size] = value
@ -333,7 +398,7 @@ def apply_elf_relative_relocation(
def convert_elf_reloc_table(
elf: ELFFile,
elf_reloc_table: ElfRelocationTable,
image_base: int,
elf_image_base: int,
sections: typing.List[PeSection],
pe_reloc_blocks: typing.Dict[int, PeRelocationBlock],
):
@ -361,7 +426,7 @@ def convert_elf_reloc_table(
if reloc["r_info_type"] == RELATIVE_RELOC:
apply_elf_relative_relocation(
reloc, image_base, sections, elf.elfclass // 8
reloc, elf_image_base, sections, elf.elfclass // 8
)
# Now that the ELF relocation has been applied, we can create a PE relocation.
@ -381,7 +446,10 @@ def convert_elf_reloc_table(
def convert_elf_relocations(
elf: ELFFile, opt: PeOptionalHeader, sections: typing.List[PeSection]
elf: ELFFile,
opt: PeOptionalHeader,
sections: typing.List[PeSection],
minimum_sections: int,
) -> typing.Optional[PeSection]:
dynamic = elf.get_section_by_name(".dynamic")
if dynamic is None:
@ -391,14 +459,49 @@ def convert_elf_relocations(
if not flags_tag["d_val"] & ENUM_DT_FLAGS_1["DF_1_PIE"]:
raise RuntimeError("ELF file is not a PIE.")
# This checks that the ELF image base is 0.
symtab = elf.get_section_by_name(".symtab")
if symtab:
exe_start = symtab.get_symbol_by_name("__executable_start")
if exe_start and exe_start[0]["st_value"] != 0:
raise RuntimeError("Unexpected ELF image base.")
opt.SizeOfHeaders = align_to(
PE_OFFSET
+ len(PE_MAGIC)
+ sizeof(PeCoffHeader)
+ sizeof(opt)
+ sizeof(PeSection) * max(len(sections) + 1, minimum_sections),
FILE_ALIGNMENT,
)
# We use the basic VMA layout from the ELF image in the PE image. This could cause the first
# section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed
# offset relative to the PE image base when applying/converting ELF relocations. Afterwards we
# just have to apply the offset to the PE addresses so that the PE relocations work correctly on
# the ELF portions of the image.
segment_offset = 0
if sections[0].VirtualAddress < opt.SizeOfHeaders:
segment_offset = align_to(
opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT
)
opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset
opt.BaseOfCode += segment_offset
if isinstance(opt, PeOptionalHeader32):
opt.BaseOfData += segment_offset
pe_reloc_blocks: typing.Dict[int, PeRelocationBlock] = {}
for reloc_type, reloc_table in dynamic.get_relocation_tables().items():
if reloc_type not in ["REL", "RELA"]:
raise RuntimeError("Unsupported relocation type {elf_reloc_type}.")
convert_elf_reloc_table(
elf, reloc_table, opt.ImageBase, sections, pe_reloc_blocks
elf, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks
)
for pe_s in sections:
pe_s.VirtualAddress += segment_offset
if len(pe_reloc_blocks) == 0:
return None
@ -413,6 +516,7 @@ def convert_elf_relocations(
n_relocs += 1
block.entries.append(PeRelocationEntry())
block.PageRVA += segment_offset
block.BlockSize = (
sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs
)
@ -423,11 +527,9 @@ def convert_elf_relocations(
pe_reloc_s = PeSection()
pe_reloc_s.Name = b".reloc"
pe_reloc_s.data = data
pe_reloc_s.VirtualAddress = next_section_address(sections)
pe_reloc_s.VirtualSize = len(data)
pe_reloc_s.SizeOfRawData = align_to(len(data), FILE_ALIGNMENT)
pe_reloc_s.VirtualAddress = align_to(
sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
)
# CNT_INITIALIZED_DATA|MEM_READ|MEM_DISCARDABLE
pe_reloc_s.Characteristics = 0x42000040
@ -495,8 +597,9 @@ def elf2efi(args: argparse.Namespace):
else:
opt.ImageBase = (0x100000000 + opt.ImageBase) & 0x1FFFF0000
sections = copy_sections(elf, opt)
pe_reloc_s = convert_elf_relocations(elf, opt, sections)
sections = convert_sections(elf, opt)
copy_sections(elf, opt, args.copy_sections, sections)
pe_reloc_s = convert_elf_relocations(elf, opt, sections, args.minimum_sections)
coff.Machine = pe_arch
coff.NumberOfSections = len(sections)
@ -506,7 +609,6 @@ def elf2efi(args: argparse.Namespace):
# and (32BIT_MACHINE or LARGE_ADDRESS_AWARE)
coff.Characteristics = 0x30E if elf.elfclass == 32 else 0x22E
opt.AddressOfEntryPoint = elf["e_entry"]
opt.SectionAlignment = SECTION_ALIGNMENT
opt.FileAlignment = FILE_ALIGNMENT
opt.MajorImageVersion = args.version_major
@ -515,18 +617,8 @@ def elf2efi(args: argparse.Namespace):
opt.MinorSubsystemVersion = args.efi_minor
opt.Subsystem = args.subsystem
opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B
opt.SizeOfImage = align_to(
sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
)
opt.SizeOfImage = next_section_address(sections)
opt.SizeOfHeaders = align_to(
PE_OFFSET
+ len(PE_MAGIC)
+ sizeof(PeCoffHeader)
+ coff.SizeOfOptionalHeader
+ sizeof(PeSection) * max(coff.NumberOfSections, args.minimum_sections),
FILE_ALIGNMENT,
)
# DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT
opt.DllCharacteristics = 0x160 if elf.elfclass == 64 else 0x140
@ -593,6 +685,12 @@ def main():
default=0,
help="Minimum number of sections to leave space for",
)
parser.add_argument(
"--copy-sections",
type=str,
default="",
help="Copy these sections if found",
)
elf2efi(parser.parse_args())