autoPatchelfHook: add support for .note.dlopen
also retain libc in rpath if it was there originally
This commit is contained in:
committed by
Will Fancher
parent
ffbecb3c19
commit
80be92660e
@@ -5,16 +5,18 @@ import os
|
||||
import pprint
|
||||
import subprocess
|
||||
import sys
|
||||
import json
|
||||
from fnmatch import fnmatch
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from itertools import chain
|
||||
from pathlib import Path, PurePath
|
||||
from typing import DefaultDict, Iterator, Optional
|
||||
from typing import DefaultDict, Generator, Iterator, Optional
|
||||
|
||||
from elftools.common.exceptions import ELFError # type: ignore
|
||||
from elftools.elf.dynamic import DynamicSection # type: ignore
|
||||
from elftools.elf.sections import NoteSection # type: ignore
|
||||
from elftools.elf.elffile import ELFFile # type: ignore
|
||||
from elftools.elf.enums import ENUM_E_TYPE, ENUM_EI_OSABI # type: ignore
|
||||
|
||||
@@ -38,7 +40,7 @@ def is_dynamic_executable(elf: ELFFile) -> bool:
|
||||
return bool(elf.get_section_by_name(".interp"))
|
||||
|
||||
|
||||
def get_dependencies(elf: ELFFile) -> list[str]:
|
||||
def get_dependencies(elf: ELFFile) -> list[list[Path]]:
|
||||
dependencies = []
|
||||
# This convoluted code is here on purpose. For some reason, using
|
||||
# elf.get_section_by_name(".dynamic") does not always return an
|
||||
@@ -46,12 +48,33 @@ def get_dependencies(elf: ELFFile) -> list[str]:
|
||||
for section in elf.iter_sections():
|
||||
if isinstance(section, DynamicSection):
|
||||
for tag in section.iter_tags('DT_NEEDED'):
|
||||
dependencies.append(tag.needed)
|
||||
dependencies.append([Path(tag.needed)])
|
||||
break # There is only one dynamic section
|
||||
|
||||
return dependencies
|
||||
|
||||
|
||||
def get_dlopen_dependencies(elf: ELFFile) -> list[list[Path]]:
|
||||
"""
|
||||
Extracts dependencies from the `.note.dlopen` section.
|
||||
This is a FreeDesktop standard to annotate binaries with libraries that it may `dlopen`.
|
||||
See https://systemd.io/ELF_DLOPEN_METADATA/
|
||||
"""
|
||||
dependencies = []
|
||||
for section in elf.iter_sections():
|
||||
if not isinstance(section, NoteSection) or section.name != ".note.dlopen":
|
||||
continue
|
||||
for note in section.iter_notes():
|
||||
if note["n_type"] != 0x407C0C0A or note["n_name"] != "FDO":
|
||||
continue
|
||||
note_desc = note["n_desc"]
|
||||
text = note_desc.decode("utf-8").rstrip("\0")
|
||||
j = json.loads(text)
|
||||
for d in j:
|
||||
dependencies.append([Path(soname) for soname in d["soname"]])
|
||||
return dependencies
|
||||
|
||||
|
||||
def get_rpath(elf: ELFFile) -> list[str]:
|
||||
# This convoluted code is here on purpose. For some reason, using
|
||||
# elf.get_section_by_name(".dynamic") does not always return an
|
||||
@@ -204,7 +227,7 @@ def auto_patchelf_file(path: Path, runtime_deps: list[Path], append_rpaths: list
|
||||
|
||||
file_is_dynamic_executable = is_dynamic_executable(elf)
|
||||
|
||||
file_dependencies = map(Path, get_dependencies(elf))
|
||||
file_dependencies = get_dependencies(elf) + get_dlopen_dependencies(elf)
|
||||
|
||||
except ELFError:
|
||||
return []
|
||||
@@ -223,24 +246,44 @@ def auto_patchelf_file(path: Path, runtime_deps: list[Path], append_rpaths: list
|
||||
# failing at the first one, because it's more useful when working
|
||||
# on a new package where you don't yet know the dependencies.
|
||||
for dep in file_dependencies:
|
||||
if dep.is_absolute() and dep.is_file():
|
||||
# This is an absolute path. If it exists, just use it.
|
||||
# Otherwise, we probably want this to produce an error when
|
||||
# checked (because just updating the rpath won't satisfy
|
||||
# it).
|
||||
continue
|
||||
elif (libc_lib / dep).is_file():
|
||||
# This library exists in libc, and will be correctly
|
||||
# resolved by the linker.
|
||||
continue
|
||||
was_found = False
|
||||
for candidate in dep:
|
||||
|
||||
if found_dependency := find_dependency(dep.name, file_arch, file_osabi):
|
||||
rpath.append(found_dependency)
|
||||
dependencies.append(Dependency(path, dep, True))
|
||||
print(f" {dep} -> found: {found_dependency}")
|
||||
else:
|
||||
dependencies.append(Dependency(path, dep, False))
|
||||
print(f" {dep} -> not found!")
|
||||
# This loop determines which candidate for a given
|
||||
# dependency can be found, and how. There may be multiple
|
||||
# candidates for a dep because of '.note.dlopen'
|
||||
# dependencies.
|
||||
#
|
||||
# 1. If a candidate is an absolute path, it is already a
|
||||
# valid dependency if that path exists, and nothing needs
|
||||
# to be done. It should be an error if that path does not exist.
|
||||
# 2. If a candidate is found in our library dependencies, that
|
||||
# dependency should be added to rpath.
|
||||
# 3. If a candidate is found in libc, it will be correctly
|
||||
# resolved by the dynamic linker automatically.
|
||||
#
|
||||
# These conditions are checked in this order, because #2
|
||||
# and #3 may both be true. In that case, we still want to
|
||||
# add the dependency to rpath, as the original binary
|
||||
# presumably had it and this should be preserved.
|
||||
|
||||
if candidate.is_absolute() and candidate.is_file():
|
||||
was_found = True
|
||||
break
|
||||
elif found_dependency := find_dependency(candidate.name, file_arch, file_osabi):
|
||||
rpath.append(found_dependency)
|
||||
dependencies.append(Dependency(path, candidate, found=True))
|
||||
print(f" {candidate} -> found: {found_dependency}")
|
||||
was_found = True
|
||||
break
|
||||
elif (libc_lib / candidate).is_file():
|
||||
was_found = True
|
||||
break
|
||||
|
||||
if not was_found:
|
||||
dep_name = dep[0] if len(dep) == 1 else f"any({', '.join(map(str, dep))})"
|
||||
dependencies.append(Dependency(path, dep_name, found=False))
|
||||
print(f" {dep_name} -> not found!")
|
||||
|
||||
rpath.extend(append_rpaths)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user