elf: relocate PT_INTERP (#42318)
Relocation of `PT_INTERP` in ELF files already happens to work from long to short path, thanks to generic binary relocation (i.e. find and replace). This PR improves it: 1. Adds logic to grow `PT_INTERP` strings through patchelf (which is only useful if the interpreter and rpath paths are the _only_ paths in the binary that need to be relocated) 2. Makes shrinking `PT_INTERP` cleaner. Before this PR when you would use Spack-built glibc as link dep, and relocate executables using its dynamic linker, you'd end up with ``` $ file exe exe: ELF 64-bit LSD pie executable, ..., interpreter /////////////////////////////////////////////////path/to/glibc/lib/ld-linux.so ``` With this PR you get something sensible: ``` $ file exe exe: ELF 64-bit LSD pie executable, ..., interpreter /path/to/glibc/lib/ld-linux.so ``` When Spack cannot modify the interpreter or rpath strings in-place, it errors out without modifying the file, and leaves both tasks to patchelf instead. Also add type hints to `elf.py`.
This commit is contained in:
parent
6d55caabe8
commit
28eea2994f
@ -542,7 +542,7 @@ def verify_patchelf(patchelf: "spack.util.executable.Executable") -> bool:
|
|||||||
return version >= spack.version.Version("0.13.1")
|
return version >= spack.version.Version("0.13.1")
|
||||||
|
|
||||||
|
|
||||||
def ensure_patchelf_in_path_or_raise() -> None:
|
def ensure_patchelf_in_path_or_raise() -> spack.util.executable.Executable:
|
||||||
"""Ensure patchelf is in the PATH or raise."""
|
"""Ensure patchelf is in the PATH or raise."""
|
||||||
# The old concretizer is not smart and we're doing its job: if the latest patchelf
|
# The old concretizer is not smart and we're doing its job: if the latest patchelf
|
||||||
# does not concretize because the compiler doesn't support C++17, we try to
|
# does not concretize because the compiler doesn't support C++17, we try to
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import IO, Optional, Tuple
|
from typing import BinaryIO, Optional, Tuple
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
from llnl.util.filesystem import BaseDirectoryVisitor, visit_directory_tree
|
from llnl.util.filesystem import BaseDirectoryVisitor, visit_directory_tree
|
||||||
@ -18,7 +18,7 @@ def should_keep(path: bytes) -> bool:
|
|||||||
return path.startswith(b"$") or (os.path.isabs(path) and os.path.lexists(path))
|
return path.startswith(b"$") or (os.path.isabs(path) and os.path.lexists(path))
|
||||||
|
|
||||||
|
|
||||||
def _drop_redundant_rpaths(f: IO) -> Optional[Tuple[bytes, bytes]]:
|
def _drop_redundant_rpaths(f: BinaryIO) -> Optional[Tuple[bytes, bytes]]:
|
||||||
"""Drop redundant entries from rpath.
|
"""Drop redundant entries from rpath.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
import macholib.mach_o
|
import macholib.mach_o
|
||||||
import macholib.MachO
|
import macholib.MachO
|
||||||
@ -47,7 +48,7 @@ def __init__(self, file_path, root_path):
|
|||||||
|
|
||||||
|
|
||||||
@memoized
|
@memoized
|
||||||
def _patchelf():
|
def _patchelf() -> Optional[executable.Executable]:
|
||||||
"""Return the full path to the patchelf binary, if available, else None."""
|
"""Return the full path to the patchelf binary, if available, else None."""
|
||||||
import spack.bootstrap
|
import spack.bootstrap
|
||||||
|
|
||||||
@ -55,9 +56,7 @@ def _patchelf():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||||
patchelf = spack.bootstrap.ensure_patchelf_in_path_or_raise()
|
return spack.bootstrap.ensure_patchelf_in_path_or_raise()
|
||||||
|
|
||||||
return patchelf.path
|
|
||||||
|
|
||||||
|
|
||||||
def _elf_rpaths_for(path):
|
def _elf_rpaths_for(path):
|
||||||
@ -340,31 +339,34 @@ def macholib_get_paths(cur_path):
|
|||||||
return (rpaths, deps, ident)
|
return (rpaths, deps, ident)
|
||||||
|
|
||||||
|
|
||||||
def _set_elf_rpaths(target, rpaths):
|
def _set_elf_rpaths_and_interpreter(
|
||||||
"""Replace the original RPATH of the target with the paths passed
|
target: str, rpaths: List[str], interpreter: Optional[str] = None
|
||||||
as arguments.
|
) -> Optional[str]:
|
||||||
|
"""Replace the original RPATH of the target with the paths passed as arguments.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
target: target executable. Must be an ELF object.
|
target: target executable. Must be an ELF object.
|
||||||
rpaths: paths to be set in the RPATH
|
rpaths: paths to be set in the RPATH
|
||||||
|
interpreter: optionally set the interpreter
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A string concatenating the stdout and stderr of the call
|
A string concatenating the stdout and stderr of the call to ``patchelf`` if it was invoked
|
||||||
to ``patchelf`` if it was invoked
|
|
||||||
"""
|
"""
|
||||||
# Join the paths using ':' as a separator
|
# Join the paths using ':' as a separator
|
||||||
rpaths_str = ":".join(rpaths)
|
rpaths_str = ":".join(rpaths)
|
||||||
|
|
||||||
patchelf, output = executable.Executable(_patchelf()), None
|
|
||||||
try:
|
try:
|
||||||
|
# TODO: error handling is not great here?
|
||||||
# TODO: revisit the use of --force-rpath as it might be conditional
|
# TODO: revisit the use of --force-rpath as it might be conditional
|
||||||
# TODO: if we want to support setting RUNPATH from binary packages
|
# TODO: if we want to support setting RUNPATH from binary packages
|
||||||
patchelf_args = ["--force-rpath", "--set-rpath", rpaths_str, target]
|
args = ["--force-rpath", "--set-rpath", rpaths_str]
|
||||||
output = patchelf(*patchelf_args, output=str, error=str)
|
if interpreter:
|
||||||
|
args.extend(["--set-interpreter", interpreter])
|
||||||
|
args.append(target)
|
||||||
|
return _patchelf()(*args, output=str, error=str)
|
||||||
except executable.ProcessError as e:
|
except executable.ProcessError as e:
|
||||||
msg = "patchelf --force-rpath --set-rpath {0} failed with error {1}"
|
tty.warn(str(e))
|
||||||
tty.warn(msg.format(target, e))
|
return None
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
def needs_binary_relocation(m_type, m_subtype):
|
def needs_binary_relocation(m_type, m_subtype):
|
||||||
@ -501,10 +503,12 @@ def new_relocate_elf_binaries(binaries, prefix_to_prefix):
|
|||||||
|
|
||||||
for path in binaries:
|
for path in binaries:
|
||||||
try:
|
try:
|
||||||
elf.replace_rpath_in_place_or_raise(path, prefix_to_prefix)
|
elf.substitute_rpath_and_pt_interp_in_place_or_raise(path, prefix_to_prefix)
|
||||||
except elf.ElfDynamicSectionUpdateFailed as e:
|
except elf.ElfCStringUpdatesFailed as e:
|
||||||
# Fall back to the old `patchelf --set-rpath` method.
|
# Fall back to `patchelf --set-rpath ... --set-interpreter ...`
|
||||||
_set_elf_rpaths(path, e.new.decode("utf-8").split(":"))
|
rpaths = e.rpath.new_value.decode("utf-8").split(":") if e.rpath else []
|
||||||
|
interpreter = e.pt_interp.new_value.decode("utf-8") if e.pt_interp else None
|
||||||
|
_set_elf_rpaths_and_interpreter(path, rpaths=rpaths, interpreter=interpreter)
|
||||||
|
|
||||||
|
|
||||||
def relocate_elf_binaries(
|
def relocate_elf_binaries(
|
||||||
@ -546,10 +550,10 @@ def relocate_elf_binaries(
|
|||||||
new_rpaths = _make_relative(new_binary, new_root, new_norm_rpaths)
|
new_rpaths = _make_relative(new_binary, new_root, new_norm_rpaths)
|
||||||
# check to see if relative rpaths are changed before rewriting
|
# check to see if relative rpaths are changed before rewriting
|
||||||
if sorted(new_rpaths) != sorted(orig_rpaths):
|
if sorted(new_rpaths) != sorted(orig_rpaths):
|
||||||
_set_elf_rpaths(new_binary, new_rpaths)
|
_set_elf_rpaths_and_interpreter(new_binary, new_rpaths)
|
||||||
else:
|
else:
|
||||||
new_rpaths = _transform_rpaths(orig_rpaths, orig_root, new_prefixes)
|
new_rpaths = _transform_rpaths(orig_rpaths, orig_root, new_prefixes)
|
||||||
_set_elf_rpaths(new_binary, new_rpaths)
|
_set_elf_rpaths_and_interpreter(new_binary, new_rpaths)
|
||||||
|
|
||||||
|
|
||||||
def make_link_relative(new_links, orig_links):
|
def make_link_relative(new_links, orig_links):
|
||||||
@ -596,7 +600,7 @@ def make_elf_binaries_relative(new_binaries, orig_binaries, orig_layout_root):
|
|||||||
orig_rpaths = _elf_rpaths_for(new_binary)
|
orig_rpaths = _elf_rpaths_for(new_binary)
|
||||||
if orig_rpaths:
|
if orig_rpaths:
|
||||||
new_rpaths = _make_relative(orig_binary, orig_layout_root, orig_rpaths)
|
new_rpaths = _make_relative(orig_binary, orig_layout_root, orig_rpaths)
|
||||||
_set_elf_rpaths(new_binary, new_rpaths)
|
_set_elf_rpaths_and_interpreter(new_binary, new_rpaths)
|
||||||
|
|
||||||
|
|
||||||
def warn_if_link_cant_be_relocated(link, target):
|
def warn_if_link_cant_be_relocated(link, target):
|
||||||
|
@ -1851,7 +1851,7 @@ def binary_with_rpaths(prefix_tmpdir):
|
|||||||
paths are encoded with `$ORIGIN` prepended.
|
paths are encoded with `$ORIGIN` prepended.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _factory(rpaths, message="Hello world!"):
|
def _factory(rpaths, message="Hello world!", dynamic_linker="/lib64/ld-linux.so.2"):
|
||||||
source = prefix_tmpdir.join("main.c")
|
source = prefix_tmpdir.join("main.c")
|
||||||
source.write(
|
source.write(
|
||||||
"""
|
"""
|
||||||
@ -1867,10 +1867,10 @@ def _factory(rpaths, message="Hello world!"):
|
|||||||
executable = source.dirpath("main.x")
|
executable = source.dirpath("main.x")
|
||||||
# Encode relative RPATHs using `$ORIGIN` as the root prefix
|
# Encode relative RPATHs using `$ORIGIN` as the root prefix
|
||||||
rpaths = [x if os.path.isabs(x) else os.path.join("$ORIGIN", x) for x in rpaths]
|
rpaths = [x if os.path.isabs(x) else os.path.join("$ORIGIN", x) for x in rpaths]
|
||||||
rpath_str = ":".join(rpaths)
|
|
||||||
opts = [
|
opts = [
|
||||||
"-Wl,--disable-new-dtags",
|
"-Wl,--disable-new-dtags",
|
||||||
"-Wl,-rpath={0}".format(rpath_str),
|
f"-Wl,-rpath={':'.join(rpaths)}",
|
||||||
|
f"-Wl,--dynamic-linker,{dynamic_linker}",
|
||||||
str(source),
|
str(source),
|
||||||
"-o",
|
"-o",
|
||||||
str(executable),
|
str(executable),
|
||||||
|
@ -46,14 +46,6 @@ def text_in_bin(text, binary):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def mock_patchelf(tmpdir, mock_executable):
|
|
||||||
def _factory(output):
|
|
||||||
return mock_executable("patchelf", output=output)
|
|
||||||
|
|
||||||
return _factory
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def make_dylib(tmpdir_factory):
|
def make_dylib(tmpdir_factory):
|
||||||
"""Create a shared library with unfriendly qualities.
|
"""Create a shared library with unfriendly qualities.
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -137,45 +136,46 @@ def test_only_header():
|
|||||||
|
|
||||||
@pytest.mark.requires_executables("gcc")
|
@pytest.mark.requires_executables("gcc")
|
||||||
@skip_unless_linux
|
@skip_unless_linux
|
||||||
def test_elf_get_and_replace_rpaths(binary_with_rpaths):
|
def test_elf_get_and_replace_rpaths_and_pt_interp(binary_with_rpaths):
|
||||||
long_rpaths = ["/very/long/prefix-a/x", "/very/long/prefix-b/y"]
|
long_paths = ["/very/long/prefix-a/x", "/very/long/prefix-b/y"]
|
||||||
executable = str(binary_with_rpaths(rpaths=long_rpaths))
|
executable = str(
|
||||||
|
binary_with_rpaths(rpaths=long_paths, dynamic_linker="/very/long/prefix-b/lib/ld.so")
|
||||||
# Before
|
|
||||||
assert elf.get_rpaths(executable) == long_rpaths
|
|
||||||
|
|
||||||
replacements = OrderedDict(
|
|
||||||
[
|
|
||||||
(b"/very/long/prefix-a", b"/short-a"),
|
|
||||||
(b"/very/long/prefix-b", b"/short-b"),
|
|
||||||
(b"/very/long", b"/dont"),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Before
|
||||||
|
assert elf.get_rpaths(executable) == long_paths
|
||||||
|
|
||||||
|
replacements = {
|
||||||
|
b"/very/long/prefix-a": b"/short-a",
|
||||||
|
b"/very/long/prefix-b": b"/short-b",
|
||||||
|
b"/very/long": b"/dont",
|
||||||
|
}
|
||||||
|
|
||||||
# Replace once: should modify the file.
|
# Replace once: should modify the file.
|
||||||
assert elf.replace_rpath_in_place_or_raise(executable, replacements)
|
assert elf.substitute_rpath_and_pt_interp_in_place_or_raise(executable, replacements)
|
||||||
|
|
||||||
# Replace twice: nothing to be done.
|
# Replace twice: nothing to be done.
|
||||||
assert not elf.replace_rpath_in_place_or_raise(executable, replacements)
|
assert not elf.substitute_rpath_and_pt_interp_in_place_or_raise(executable, replacements)
|
||||||
|
|
||||||
# Verify the rpaths were modified correctly
|
# Verify the rpaths were modified correctly
|
||||||
assert elf.get_rpaths(executable) == ["/short-a/x", "/short-b/y"]
|
assert elf.get_rpaths(executable) == ["/short-a/x", "/short-b/y"]
|
||||||
|
assert elf.get_interpreter(executable) == "/short-b/lib/ld.so"
|
||||||
|
|
||||||
# Going back to long rpaths should fail, since we've added trailing \0
|
# Going back to long rpaths should fail, since we've added trailing \0
|
||||||
# bytes, and replacement can't assume it can write back in repeated null
|
# bytes, and replacement can't assume it can write back in repeated null
|
||||||
# bytes -- it may correspond to zero-length strings for example.
|
# bytes -- it may correspond to zero-length strings for example.
|
||||||
with pytest.raises(
|
with pytest.raises(elf.ElfCStringUpdatesFailed) as info:
|
||||||
elf.ElfDynamicSectionUpdateFailed,
|
elf.substitute_rpath_and_pt_interp_in_place_or_raise(
|
||||||
match="New rpath /very/long/prefix-a/x:/very/long/prefix-b/y is "
|
executable, {b"/short-a": b"/very/long/prefix-a", b"/short-b": b"/very/long/prefix-b"}
|
||||||
"longer than old rpath /short-a/x:/short-b/y",
|
|
||||||
):
|
|
||||||
elf.replace_rpath_in_place_or_raise(
|
|
||||||
executable,
|
|
||||||
OrderedDict(
|
|
||||||
[(b"/short-a", b"/very/long/prefix-a"), (b"/short-b", b"/very/long/prefix-b")]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert info.value.rpath is not None
|
||||||
|
assert info.value.pt_interp is not None
|
||||||
|
assert info.value.rpath.old_value == b"/short-a/x:/short-b/y"
|
||||||
|
assert info.value.rpath.new_value == b"/very/long/prefix-a/x:/very/long/prefix-b/y"
|
||||||
|
assert info.value.pt_interp.old_value == b"/short-b/lib/ld.so"
|
||||||
|
assert info.value.pt_interp.new_value == b"/very/long/prefix-b/lib/ld.so"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.requires_executables("gcc")
|
@pytest.mark.requires_executables("gcc")
|
||||||
@skip_unless_linux
|
@skip_unless_linux
|
||||||
|
@ -6,53 +6,59 @@
|
|||||||
import bisect
|
import bisect
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
from collections import namedtuple
|
|
||||||
from struct import calcsize, unpack, unpack_from
|
from struct import calcsize, unpack, unpack_from
|
||||||
|
from typing import BinaryIO, Dict, List, NamedTuple, Optional, Pattern, Tuple
|
||||||
|
|
||||||
ElfHeader = namedtuple(
|
|
||||||
"ElfHeader",
|
|
||||||
[
|
|
||||||
"e_type",
|
|
||||||
"e_machine",
|
|
||||||
"e_version",
|
|
||||||
"e_entry",
|
|
||||||
"e_phoff",
|
|
||||||
"e_shoff",
|
|
||||||
"e_flags",
|
|
||||||
"e_ehsize",
|
|
||||||
"e_phentsize",
|
|
||||||
"e_phnum",
|
|
||||||
"e_shentsize",
|
|
||||||
"e_shnum",
|
|
||||||
"e_shstrndx",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
SectionHeader = namedtuple(
|
class ElfHeader(NamedTuple):
|
||||||
"SectionHeader",
|
e_type: int
|
||||||
[
|
e_machine: int
|
||||||
"sh_name",
|
e_version: int
|
||||||
"sh_type",
|
e_entry: int
|
||||||
"sh_flags",
|
e_phoff: int
|
||||||
"sh_addr",
|
e_shoff: int
|
||||||
"sh_offset",
|
e_flags: int
|
||||||
"sh_size",
|
e_ehsize: int
|
||||||
"sh_link",
|
e_phentsize: int
|
||||||
"sh_info",
|
e_phnum: int
|
||||||
"sh_addralign",
|
e_shentsize: int
|
||||||
"sh_entsize",
|
e_shnum: int
|
||||||
],
|
e_shstrndx: int
|
||||||
)
|
|
||||||
|
|
||||||
ProgramHeader32 = namedtuple(
|
|
||||||
"ProgramHeader32",
|
|
||||||
["p_type", "p_offset", "p_vaddr", "p_paddr", "p_filesz", "p_memsz", "p_flags", "p_align"],
|
|
||||||
)
|
|
||||||
|
|
||||||
ProgramHeader64 = namedtuple(
|
class SectionHeader(NamedTuple):
|
||||||
"ProgramHeader64",
|
sh_name: int
|
||||||
["p_type", "p_flags", "p_offset", "p_vaddr", "p_paddr", "p_filesz", "p_memsz", "p_align"],
|
sh_type: int
|
||||||
)
|
sh_flags: int
|
||||||
|
sh_addr: int
|
||||||
|
sh_offset: int
|
||||||
|
sh_size: int
|
||||||
|
sh_link: int
|
||||||
|
sh_info: int
|
||||||
|
sh_addralign: int
|
||||||
|
sh_entsize: int
|
||||||
|
|
||||||
|
|
||||||
|
class ProgramHeader32(NamedTuple):
|
||||||
|
p_type: int
|
||||||
|
p_offset: int
|
||||||
|
p_vaddr: int
|
||||||
|
p_paddr: int
|
||||||
|
p_filesz: int
|
||||||
|
p_memsz: int
|
||||||
|
p_flags: int
|
||||||
|
p_align: int
|
||||||
|
|
||||||
|
|
||||||
|
class ProgramHeader64(NamedTuple):
|
||||||
|
p_type: int
|
||||||
|
p_flags: int
|
||||||
|
p_offset: int
|
||||||
|
p_vaddr: int
|
||||||
|
p_paddr: int
|
||||||
|
p_filesz: int
|
||||||
|
p_memsz: int
|
||||||
|
p_align: int
|
||||||
|
|
||||||
|
|
||||||
class ELF_CONSTANTS:
|
class ELF_CONSTANTS:
|
||||||
@ -78,6 +84,31 @@ class ELF_CONSTANTS:
|
|||||||
class ElfFile:
|
class ElfFile:
|
||||||
"""Parsed ELF file."""
|
"""Parsed ELF file."""
|
||||||
|
|
||||||
|
is_64_bit: bool
|
||||||
|
is_little_endian: bool
|
||||||
|
byte_order: str
|
||||||
|
elf_hdr: ElfHeader
|
||||||
|
pt_load: List[Tuple[int, int]]
|
||||||
|
has_pt_interp: bool
|
||||||
|
pt_interp_p_offset: int
|
||||||
|
pt_interp_p_filesz: int
|
||||||
|
pt_interp_str: bytes
|
||||||
|
has_pt_dynamic: bool
|
||||||
|
pt_dynamic_p_offset: int
|
||||||
|
pt_dynamic_p_filesz: int
|
||||||
|
pt_dynamic_strtab_offset: int
|
||||||
|
has_rpath: bool
|
||||||
|
dt_rpath_offset: int
|
||||||
|
dt_rpath_str: bytes
|
||||||
|
rpath_strtab_offset: int
|
||||||
|
is_runpath: bool
|
||||||
|
has_needed: bool
|
||||||
|
dt_needed_strtab_offsets: List[int]
|
||||||
|
dt_needed_strs: List[bytes]
|
||||||
|
has_soname: bool
|
||||||
|
dt_soname_strtab_offset: int
|
||||||
|
dt_soname_str: bytes
|
||||||
|
|
||||||
__slots__ = [
|
__slots__ = [
|
||||||
"is_64_bit",
|
"is_64_bit",
|
||||||
"is_little_endian",
|
"is_little_endian",
|
||||||
@ -120,13 +151,13 @@ def __init__(self):
|
|||||||
self.has_pt_interp = False
|
self.has_pt_interp = False
|
||||||
|
|
||||||
|
|
||||||
def parse_c_string(byte_string, start=0):
|
def parse_c_string(byte_string: bytes, start: int = 0) -> bytes:
|
||||||
"""
|
"""
|
||||||
Retrieve a C-string at a given offset in a byte string
|
Retrieve a C-string at a given offset in a byte string
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
byte_string (bytes): String
|
byte_string: String
|
||||||
start (int): Offset into the string
|
start: Offset into the string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bytes: A copy of the C-string excluding the terminating null byte
|
bytes: A copy of the C-string excluding the terminating null byte
|
||||||
@ -137,15 +168,15 @@ def parse_c_string(byte_string, start=0):
|
|||||||
return byte_string[start:str_end]
|
return byte_string[start:str_end]
|
||||||
|
|
||||||
|
|
||||||
def read_exactly(f, num_bytes, msg):
|
def read_exactly(f: BinaryIO, num_bytes: int, msg: str) -> bytes:
|
||||||
"""
|
"""
|
||||||
Read exactly num_bytes at the current offset, otherwise raise
|
Read exactly num_bytes at the current offset, otherwise raise
|
||||||
a parsing error with the given error message.
|
a parsing error with the given error message.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
f: file handle
|
f: file handle
|
||||||
num_bytes (int): Number of bytes to read
|
num_bytes: Number of bytes to read
|
||||||
msg (str): Error to show when bytes cannot be read
|
msg: Error to show when bytes cannot be read
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bytes: the ``num_bytes`` bytes that were read.
|
bytes: the ``num_bytes`` bytes that were read.
|
||||||
@ -156,19 +187,18 @@ def read_exactly(f, num_bytes, msg):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def parse_program_headers(f, elf):
|
def parse_program_headers(f: BinaryIO, elf: ElfFile) -> None:
|
||||||
"""
|
"""
|
||||||
Parse program headers
|
Parse program headers
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
f: file handle
|
f: file handle
|
||||||
elf (ElfFile): ELF file parser data
|
elf: ELF file parser data
|
||||||
"""
|
"""
|
||||||
# Forward to the program header
|
# Forward to the program header
|
||||||
f.seek(elf.elf_hdr.e_phoff)
|
f.seek(elf.elf_hdr.e_phoff)
|
||||||
|
|
||||||
# Here we have to make a mapping from virtual address to offset in the file.
|
# Here we have to make a mapping from virtual address to offset in the file.
|
||||||
ProgramHeader = ProgramHeader64 if elf.is_64_bit else ProgramHeader32
|
|
||||||
ph_fmt = elf.byte_order + ("LLQQQQQQ" if elf.is_64_bit else "LLLLLLLL")
|
ph_fmt = elf.byte_order + ("LLQQQQQQ" if elf.is_64_bit else "LLLLLLLL")
|
||||||
ph_size = calcsize(ph_fmt)
|
ph_size = calcsize(ph_fmt)
|
||||||
ph_num = elf.elf_hdr.e_phnum
|
ph_num = elf.elf_hdr.e_phnum
|
||||||
@ -176,28 +206,31 @@ def parse_program_headers(f, elf):
|
|||||||
# Read all program headers in one go
|
# Read all program headers in one go
|
||||||
data = read_exactly(f, ph_num * ph_size, "Malformed program header")
|
data = read_exactly(f, ph_num * ph_size, "Malformed program header")
|
||||||
|
|
||||||
|
ProgramHeader = ProgramHeader64 if elf.is_64_bit else ProgramHeader32
|
||||||
|
|
||||||
for i in range(ph_num):
|
for i in range(ph_num):
|
||||||
ph = ProgramHeader._make(unpack_from(ph_fmt, data, i * ph_size))
|
# mypy currently does not understand the union of two named tuples with equal fields
|
||||||
|
ph = ProgramHeader(*unpack_from(ph_fmt, data, i * ph_size))
|
||||||
|
|
||||||
# Skip segments of size 0; we don't distinguish between missing segment and
|
# Skip segments of size 0; we don't distinguish between missing segment and
|
||||||
# empty segments. I've see an empty PT_DYNAMIC section for an ELF file that
|
# empty segments. I've see an empty PT_DYNAMIC section for an ELF file that
|
||||||
# contained debug data.
|
# contained debug data.
|
||||||
if ph.p_filesz == 0:
|
if ph.p_filesz == 0: # type: ignore
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# For PT_LOAD entries: Save offsets and virtual addrs of the loaded ELF segments
|
# For PT_LOAD entries: Save offsets and virtual addrs of the loaded ELF segments
|
||||||
# This way we can map offsets by virtual address to offsets in the file.
|
# This way we can map offsets by virtual address to offsets in the file.
|
||||||
if ph.p_type == ELF_CONSTANTS.PT_LOAD:
|
if ph.p_type == ELF_CONSTANTS.PT_LOAD: # type: ignore
|
||||||
elf.pt_load.append((ph.p_offset, ph.p_vaddr))
|
elf.pt_load.append((ph.p_offset, ph.p_vaddr)) # type: ignore
|
||||||
|
|
||||||
elif ph.p_type == ELF_CONSTANTS.PT_INTERP:
|
elif ph.p_type == ELF_CONSTANTS.PT_INTERP: # type: ignore
|
||||||
elf.pt_interp_p_offset = ph.p_offset
|
elf.pt_interp_p_offset = ph.p_offset # type: ignore
|
||||||
elf.pt_interp_p_filesz = ph.p_filesz
|
elf.pt_interp_p_filesz = ph.p_filesz # type: ignore
|
||||||
elf.has_pt_interp = True
|
elf.has_pt_interp = True
|
||||||
|
|
||||||
elif ph.p_type == ELF_CONSTANTS.PT_DYNAMIC:
|
elif ph.p_type == ELF_CONSTANTS.PT_DYNAMIC: # type: ignore
|
||||||
elf.pt_dynamic_p_offset = ph.p_offset
|
elf.pt_dynamic_p_offset = ph.p_offset # type: ignore
|
||||||
elf.pt_dynamic_p_filesz = ph.p_filesz
|
elf.pt_dynamic_p_filesz = ph.p_filesz # type: ignore
|
||||||
elf.has_pt_dynamic = True
|
elf.has_pt_dynamic = True
|
||||||
|
|
||||||
# The linker sorts PT_LOAD segments by vaddr, but let's do it just to be sure, since
|
# The linker sorts PT_LOAD segments by vaddr, but let's do it just to be sure, since
|
||||||
@ -205,27 +238,27 @@ def parse_program_headers(f, elf):
|
|||||||
elf.pt_load.sort(key=lambda x: x[1])
|
elf.pt_load.sort(key=lambda x: x[1])
|
||||||
|
|
||||||
|
|
||||||
def parse_pt_interp(f, elf):
|
def parse_pt_interp(f: BinaryIO, elf: ElfFile) -> None:
|
||||||
"""
|
"""
|
||||||
Parse the interpreter (i.e. absolute path to the dynamic linker)
|
Parse the interpreter (i.e. absolute path to the dynamic linker)
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
f: file handle
|
f: file handle
|
||||||
elf (ElfFile): ELF file parser data
|
elf: ELF file parser data
|
||||||
"""
|
"""
|
||||||
f.seek(elf.pt_interp_p_offset)
|
f.seek(elf.pt_interp_p_offset)
|
||||||
data = read_exactly(f, elf.pt_interp_p_filesz, "Malformed PT_INTERP entry")
|
data = read_exactly(f, elf.pt_interp_p_filesz, "Malformed PT_INTERP entry")
|
||||||
elf.pt_interp_str = parse_c_string(data)
|
elf.pt_interp_str = parse_c_string(data)
|
||||||
|
|
||||||
|
|
||||||
def find_strtab_size_at_offset(f, elf, offset):
|
def find_strtab_size_at_offset(f: BinaryIO, elf: ElfFile, offset: int) -> int:
|
||||||
"""
|
"""
|
||||||
Retrieve the size of a string table section at a particular known offset
|
Retrieve the size of a string table section at a particular known offset
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
f: file handle
|
f: file handle
|
||||||
elf (ElfFile): ELF file parser data
|
elf: ELF file parser data
|
||||||
offset (int): offset of the section in the file (i.e. ``sh_offset``)
|
offset: offset of the section in the file (i.e. ``sh_offset``)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: the size of the string table in bytes
|
int: the size of the string table in bytes
|
||||||
@ -235,50 +268,49 @@ def find_strtab_size_at_offset(f, elf, offset):
|
|||||||
f.seek(elf.elf_hdr.e_shoff)
|
f.seek(elf.elf_hdr.e_shoff)
|
||||||
for _ in range(elf.elf_hdr.e_shnum):
|
for _ in range(elf.elf_hdr.e_shnum):
|
||||||
data = read_exactly(f, section_hdr_size, "Malformed section header")
|
data = read_exactly(f, section_hdr_size, "Malformed section header")
|
||||||
sh = SectionHeader._make(unpack(section_hdr_fmt, data))
|
sh = SectionHeader(*unpack(section_hdr_fmt, data))
|
||||||
if sh.sh_type == ELF_CONSTANTS.SHT_STRTAB and sh.sh_offset == offset:
|
if sh.sh_type == ELF_CONSTANTS.SHT_STRTAB and sh.sh_offset == offset:
|
||||||
return sh.sh_size
|
return sh.sh_size
|
||||||
|
|
||||||
raise ElfParsingError("Could not determine strtab size")
|
raise ElfParsingError("Could not determine strtab size")
|
||||||
|
|
||||||
|
|
||||||
def retrieve_strtab(f, elf, offset):
|
def retrieve_strtab(f: BinaryIO, elf: ElfFile, offset: int) -> bytes:
|
||||||
"""
|
"""
|
||||||
Read a full string table at the given offset, which
|
Read a full string table at the given offset, which
|
||||||
requires looking it up in the section headers.
|
requires looking it up in the section headers.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
elf (ElfFile): ELF file parser data
|
elf: ELF file parser data
|
||||||
vaddr (int): virtual address
|
vaddr: virtual address
|
||||||
|
|
||||||
Returns:
|
Returns: file offset
|
||||||
bytes: file offset
|
|
||||||
"""
|
"""
|
||||||
size = find_strtab_size_at_offset(f, elf, offset)
|
size = find_strtab_size_at_offset(f, elf, offset)
|
||||||
f.seek(offset)
|
f.seek(offset)
|
||||||
return read_exactly(f, size, "Could not read string table")
|
return read_exactly(f, size, "Could not read string table")
|
||||||
|
|
||||||
|
|
||||||
def vaddr_to_offset(elf, vaddr):
|
def vaddr_to_offset(elf: ElfFile, vaddr: int) -> int:
|
||||||
"""
|
"""
|
||||||
Given a virtual address, find the corresponding offset in the ELF file itself.
|
Given a virtual address, find the corresponding offset in the ELF file itself.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
elf (ElfFile): ELF file parser data
|
elf: ELF file parser data
|
||||||
vaddr (int): virtual address
|
vaddr: virtual address
|
||||||
"""
|
"""
|
||||||
idx = bisect.bisect_right([p_vaddr for (p_offset, p_vaddr) in elf.pt_load], vaddr) - 1
|
idx = bisect.bisect_right([p_vaddr for (p_offset, p_vaddr) in elf.pt_load], vaddr) - 1
|
||||||
p_offset, p_vaddr = elf.pt_load[idx]
|
p_offset, p_vaddr = elf.pt_load[idx]
|
||||||
return p_offset - p_vaddr + vaddr
|
return p_offset - p_vaddr + vaddr
|
||||||
|
|
||||||
|
|
||||||
def parse_pt_dynamic(f, elf):
|
def parse_pt_dynamic(f: BinaryIO, elf: ElfFile) -> None:
|
||||||
"""
|
"""
|
||||||
Parse the dynamic section of an ELF file
|
Parse the dynamic section of an ELF file
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
f: file handle
|
f: file handle
|
||||||
elf (ElfFile): ELF file parse data
|
elf: ELF file parse data
|
||||||
"""
|
"""
|
||||||
dynamic_array_fmt = elf.byte_order + ("qQ" if elf.is_64_bit else "lL")
|
dynamic_array_fmt = elf.byte_order + ("qQ" if elf.is_64_bit else "lL")
|
||||||
dynamic_array_size = calcsize(dynamic_array_fmt)
|
dynamic_array_size = calcsize(dynamic_array_fmt)
|
||||||
@ -347,7 +379,7 @@ def parse_pt_dynamic(f, elf):
|
|||||||
elf.dt_rpath_str = parse_c_string(string_table, elf.rpath_strtab_offset)
|
elf.dt_rpath_str = parse_c_string(string_table, elf.rpath_strtab_offset)
|
||||||
|
|
||||||
|
|
||||||
def parse_header(f, elf):
|
def parse_header(f: BinaryIO, elf: ElfFile) -> None:
|
||||||
# Read the 32/64 bit class independent part of the header and validate
|
# Read the 32/64 bit class independent part of the header and validate
|
||||||
e_ident = f.read(16)
|
e_ident = f.read(16)
|
||||||
|
|
||||||
@ -374,10 +406,12 @@ def parse_header(f, elf):
|
|||||||
elf_header_fmt = elf.byte_order + ("HHLQQQLHHHHHH" if elf.is_64_bit else "HHLLLLLHHHHHH")
|
elf_header_fmt = elf.byte_order + ("HHLQQQLHHHHHH" if elf.is_64_bit else "HHLLLLLHHHHHH")
|
||||||
hdr_size = calcsize(elf_header_fmt)
|
hdr_size = calcsize(elf_header_fmt)
|
||||||
data = read_exactly(f, hdr_size, "ELF header malformed")
|
data = read_exactly(f, hdr_size, "ELF header malformed")
|
||||||
elf.elf_hdr = ElfHeader._make(unpack(elf_header_fmt, data))
|
elf.elf_hdr = ElfHeader(*unpack(elf_header_fmt, data))
|
||||||
|
|
||||||
|
|
||||||
def _do_parse_elf(f, interpreter=True, dynamic_section=True, only_header=False):
|
def _do_parse_elf(
|
||||||
|
f: BinaryIO, interpreter: bool = True, dynamic_section: bool = True, only_header: bool = False
|
||||||
|
) -> ElfFile:
|
||||||
# We don't (yet?) allow parsing ELF files at a nonzero offset, we just
|
# We don't (yet?) allow parsing ELF files at a nonzero offset, we just
|
||||||
# jump to absolute offsets as they are specified in the ELF file.
|
# jump to absolute offsets as they are specified in the ELF file.
|
||||||
if f.tell() != 0:
|
if f.tell() != 0:
|
||||||
@ -406,7 +440,12 @@ def _do_parse_elf(f, interpreter=True, dynamic_section=True, only_header=False):
|
|||||||
return elf
|
return elf
|
||||||
|
|
||||||
|
|
||||||
def parse_elf(f, interpreter=False, dynamic_section=False, only_header=False):
|
def parse_elf(
|
||||||
|
f: BinaryIO,
|
||||||
|
interpreter: bool = False,
|
||||||
|
dynamic_section: bool = False,
|
||||||
|
only_header: bool = False,
|
||||||
|
) -> ElfFile:
|
||||||
"""Given a file handle f for an ELF file opened in binary mode, return an ElfFile
|
"""Given a file handle f for an ELF file opened in binary mode, return an ElfFile
|
||||||
object that is stores data about rpaths"""
|
object that is stores data about rpaths"""
|
||||||
try:
|
try:
|
||||||
@ -417,28 +456,30 @@ def parse_elf(f, interpreter=False, dynamic_section=False, only_header=False):
|
|||||||
raise ElfParsingError("Malformed ELF file")
|
raise ElfParsingError("Malformed ELF file")
|
||||||
|
|
||||||
|
|
||||||
def get_rpaths(path):
|
def get_rpaths(path: str) -> Optional[List[str]]:
|
||||||
"""Returns list of rpaths of the given file as UTF-8 strings, or None if the file
|
"""Returns list of rpaths of the given file as UTF-8 strings, or None if not set."""
|
||||||
does not have any rpaths."""
|
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
elf = parse_elf(f, interpreter=False, dynamic_section=True)
|
elf = parse_elf(f, interpreter=False, dynamic_section=True)
|
||||||
|
return elf.dt_rpath_str.decode("utf-8").split(":") if elf.has_rpath else None
|
||||||
except ElfParsingError:
|
except ElfParsingError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not elf.has_rpath:
|
|
||||||
|
def get_interpreter(path: str) -> Optional[str]:
|
||||||
|
"""Returns the interpreter of the given file as UTF-8 string, or None if not set."""
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
elf = parse_elf(f, interpreter=True, dynamic_section=False)
|
||||||
|
return elf.pt_interp_str.decode("utf-8") if elf.has_pt_interp else None
|
||||||
|
except ElfParsingError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# If it does, split the string in components
|
|
||||||
rpath = elf.dt_rpath_str
|
|
||||||
rpath = rpath.decode("utf-8")
|
|
||||||
return rpath.split(":")
|
|
||||||
|
|
||||||
|
def delete_rpath(path: str) -> None:
|
||||||
def delete_rpath(path):
|
"""Modifies a binary to remove the rpath. It zeros out the rpath string and also drops the
|
||||||
"""Modifies a binary to remove the rpath. It zeros out the rpath string
|
DT_R(UN)PATH entry from the dynamic section, so it doesn't show up in 'readelf -d file', nor
|
||||||
and also drops the DT_R(UN)PATH entry from the dynamic section, so it doesn't
|
in 'strings file'."""
|
||||||
show up in 'readelf -d file', nor in 'strings file'."""
|
|
||||||
with open(path, "rb+") as f:
|
with open(path, "rb+") as f:
|
||||||
elf = parse_elf(f, interpreter=False, dynamic_section=True)
|
elf = parse_elf(f, interpreter=False, dynamic_section=True)
|
||||||
|
|
||||||
@ -476,75 +517,136 @@ def delete_rpath(path):
|
|||||||
old_offset += dynamic_array_size
|
old_offset += dynamic_array_size
|
||||||
|
|
||||||
|
|
||||||
def replace_rpath_in_place_or_raise(path, substitutions):
|
class CStringType:
|
||||||
|
PT_INTERP = 1
|
||||||
|
RPATH = 2
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateCStringAction:
|
||||||
|
def __init__(self, old_value: bytes, new_value: bytes, offset: int):
|
||||||
|
self.old_value = old_value
|
||||||
|
self.new_value = new_value
|
||||||
|
self.offset = offset
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inplace(self) -> bool:
|
||||||
|
return len(self.new_value) <= len(self.old_value)
|
||||||
|
|
||||||
|
def apply(self, f: BinaryIO) -> None:
|
||||||
|
assert self.inplace
|
||||||
|
|
||||||
|
f.seek(self.offset)
|
||||||
|
f.write(self.new_value)
|
||||||
|
|
||||||
|
# We zero out the bits we shortened because (a) it should be a
|
||||||
|
# C-string and (b) it's nice not to have spurious parts of old
|
||||||
|
# paths in the output of `strings file`. Note that we're all
|
||||||
|
# good when pad == 0; the original terminating null is used.
|
||||||
|
f.write(b"\x00" * (len(self.old_value) - len(self.new_value)))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_rpath_substitution(
|
||||||
|
elf: ElfFile, regex: Pattern, substitutions: Dict[bytes, bytes]
|
||||||
|
) -> Optional[UpdateCStringAction]:
|
||||||
|
"""Make rpath substitutions in-place."""
|
||||||
|
# If there's no RPATH, then there's no need to replace anything.
|
||||||
|
if not elf.has_rpath:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get the non-empty rpaths. Sometimes there's a bunch of trailing
|
||||||
|
# colons ::::: used for padding, we don't add them back to make it
|
||||||
|
# more likely that the string doesn't grow.
|
||||||
|
rpaths = list(filter(len, elf.dt_rpath_str.split(b":")))
|
||||||
|
|
||||||
|
num_rpaths = len(rpaths)
|
||||||
|
|
||||||
|
if num_rpaths == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
for i in range(num_rpaths):
|
||||||
|
old_rpath = rpaths[i]
|
||||||
|
match = regex.match(old_rpath)
|
||||||
|
if match:
|
||||||
|
changed = True
|
||||||
|
rpaths[i] = substitutions[match.group()] + old_rpath[match.end() :]
|
||||||
|
|
||||||
|
# Nothing to replace!
|
||||||
|
if not changed:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return UpdateCStringAction(
|
||||||
|
old_value=elf.dt_rpath_str,
|
||||||
|
new_value=b":".join(rpaths),
|
||||||
|
# The rpath is at a given offset in the string table used by the dynamic section.
|
||||||
|
offset=elf.pt_dynamic_strtab_offset + elf.rpath_strtab_offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pt_interp_substitution(
|
||||||
|
elf: ElfFile, regex: Pattern, substitutions: Dict[bytes, bytes]
|
||||||
|
) -> Optional[UpdateCStringAction]:
|
||||||
|
"""Make interpreter substitutions in-place."""
|
||||||
|
if not elf.has_pt_interp:
|
||||||
|
return None
|
||||||
|
|
||||||
|
match = regex.match(elf.pt_interp_str)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return UpdateCStringAction(
|
||||||
|
old_value=elf.pt_interp_str,
|
||||||
|
new_value=substitutions[match.group()] + elf.pt_interp_str[match.end() :],
|
||||||
|
offset=elf.pt_interp_p_offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def substitute_rpath_and_pt_interp_in_place_or_raise(
|
||||||
|
path: str, substitutions: Dict[bytes, bytes]
|
||||||
|
) -> bool:
|
||||||
|
"""Returns true if the rpath and interpreter were modified, false if there was nothing to do.
|
||||||
|
Raises ElfCStringUpdatesFailed if the ELF file cannot be updated in-place. This exception
|
||||||
|
contains a list of actions to perform with other tools. The file is left untouched in this
|
||||||
|
case."""
|
||||||
regex = re.compile(b"|".join(re.escape(p) for p in substitutions.keys()))
|
regex = re.compile(b"|".join(re.escape(p) for p in substitutions.keys()))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, "rb+") as f:
|
with open(path, "rb+") as f:
|
||||||
elf = parse_elf(f, interpreter=False, dynamic_section=True)
|
elf = parse_elf(f, interpreter=True, dynamic_section=True)
|
||||||
|
|
||||||
# If there's no RPATH, then there's no need to replace anything.
|
# Get the actions to perform.
|
||||||
if not elf.has_rpath:
|
rpath = _get_rpath_substitution(elf, regex, substitutions)
|
||||||
|
pt_interp = _get_pt_interp_substitution(elf, regex, substitutions)
|
||||||
|
|
||||||
|
# Nothing to do.
|
||||||
|
if not rpath and not pt_interp:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Get the non-empty rpaths. Sometimes there's a bunch of trailing
|
# If we can't update in-place, leave it to other tools, don't do partial updates.
|
||||||
# colons ::::: used for padding, we don't add them back to make it
|
if rpath and not rpath.inplace or pt_interp and not pt_interp.inplace:
|
||||||
# more likely that the string doesn't grow.
|
raise ElfCStringUpdatesFailed(rpath, pt_interp)
|
||||||
rpaths = list(filter(len, elf.dt_rpath_str.split(b":")))
|
|
||||||
|
|
||||||
num_rpaths = len(rpaths)
|
# Otherwise, apply the updates.
|
||||||
|
if rpath:
|
||||||
|
rpath.apply(f)
|
||||||
|
|
||||||
if num_rpaths == 0:
|
if pt_interp:
|
||||||
return False
|
pt_interp.apply(f)
|
||||||
|
|
||||||
changed = False
|
|
||||||
for i in range(num_rpaths):
|
|
||||||
old_rpath = rpaths[i]
|
|
||||||
match = regex.match(old_rpath)
|
|
||||||
if match:
|
|
||||||
changed = True
|
|
||||||
rpaths[i] = substitutions[match.group()] + old_rpath[match.end() :]
|
|
||||||
|
|
||||||
# Nothing to replace!
|
|
||||||
if not changed:
|
|
||||||
return False
|
|
||||||
|
|
||||||
new_rpath_string = b":".join(rpaths)
|
|
||||||
|
|
||||||
pad = len(elf.dt_rpath_str) - len(new_rpath_string)
|
|
||||||
|
|
||||||
if pad < 0:
|
|
||||||
raise ElfDynamicSectionUpdateFailed(elf.dt_rpath_str, new_rpath_string)
|
|
||||||
|
|
||||||
# We zero out the bits we shortened because (a) it should be a
|
|
||||||
# C-string and (b) it's nice not to have spurious parts of old
|
|
||||||
# paths in the output of `strings file`. Note that we're all
|
|
||||||
# good when pad == 0; the original terminating null is used.
|
|
||||||
new_rpath_string += b"\x00" * pad
|
|
||||||
|
|
||||||
# The rpath is at a given offset in the string table used by the
|
|
||||||
# dynamic section.
|
|
||||||
rpath_offset = elf.pt_dynamic_strtab_offset + elf.rpath_strtab_offset
|
|
||||||
|
|
||||||
f.seek(rpath_offset)
|
|
||||||
f.write(new_rpath_string)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except ElfParsingError:
|
except ElfParsingError:
|
||||||
# This just means the file wasnt an elf file, so there's no point
|
# This just means the file wasn't an elf file, so there's no point
|
||||||
# in updating its rpath anyways; ignore this problem.
|
# in updating its rpath anyways; ignore this problem.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ElfDynamicSectionUpdateFailed(Exception):
|
class ElfCStringUpdatesFailed(Exception):
|
||||||
def __init__(self, old, new):
|
def __init__(
|
||||||
self.old = old
|
self, rpath: Optional[UpdateCStringAction], pt_interp: Optional[UpdateCStringAction]
|
||||||
self.new = new
|
):
|
||||||
super().__init__(
|
self.rpath = rpath
|
||||||
"New rpath {} is longer than old rpath {}".format(
|
self.pt_interp = pt_interp
|
||||||
new.decode("utf-8"), old.decode("utf-8")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ElfParsingError(Exception):
|
class ElfParsingError(Exception):
|
||||||
|
Loading…
Reference in New Issue
Block a user