From 9449c95ad44cc12afcdb9f1f0652198ece4cd2e0 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Thu, 1 Jun 2023 11:28:58 +0300 Subject: [PATCH] implement elf dynamic linking check --- setup.py | 1 + src/ahriman/models/package_archive.py | 95 +++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/ahriman/models/package_archive.py diff --git a/setup.py b/setup.py index 436a1020..c3b5a0c0 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ setup( "cerberus", "inflection", "passlib", + "pyelftools", "requests", "srcinfo", ], diff --git a/src/ahriman/models/package_archive.py b/src/ahriman/models/package_archive.py new file mode 100644 index 00000000..b2e9956f --- /dev/null +++ b/src/ahriman/models/package_archive.py @@ -0,0 +1,95 @@ +# +# Copyright (c) 2021-2023 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from dataclasses import dataclass +from pathlib import Path +from typing import IO + +from elftools.elf.elffile import ELFFile +from elftools.elf.dynamic import DynamicSection +from pyalpm import Package # type: ignore[import] + +from ahriman.core.util import walk + + +@dataclass +class PackageArchive: + """ + helper for package archives + """ + + package: Package + + @staticmethod + def get_depends_paths(package_base: str, root: Path) -> tuple[set[str], set[Path]]: + dependencies = set() + roots = set() + + package_dir = root / "build" / package_base / "pkg" + for path in walk(package_dir): + dependencies.update(PackageArchive.get_dynamic(path)) + roots.update(path.relative_to(package_dir).parents) + + return dependencies, roots + + @staticmethod + def get_dynamic(binary_path: Path) -> list[str]: + with binary_path.open("rb") as binary_file: + if not PackageArchive.is_elf(binary_file): + return [] + + elf_file = ELFFile(binary_file) + dynamic_section = next((section for section in elf_file.iter_sections() if isinstance(section, DynamicSection)), None) + if dynamic_section is None: + return [] + + return [tag.needed for tag in dynamic_section.iter_tags() if tag.entry.d_tag == "DT_NEEDED"] + + @staticmethod + def get_filesystem(root: Path) -> dict[str, tuple[list[Path], list[Path]]]: + result = {} + for path in filter(lambda fn: fn.name == "files", walk(root / "var" / "lib" / "pacman" / "local")): + package, *_ = path.parent.name.rsplit("-", 2) + + directories = [] + files = [] + for entry in path.read_text(encoding="utf8").splitlines(): + if not entry or entry == "%FILES%": + continue # header + entry_path = Path(entry) + if entry_path.name == "": + directories.append(entry_path) + else: + files.append(entry_path) + + result[package] = (directories, files) + + return result + + @staticmethod + def is_elf(descriptor: IO[bytes]) -> bool: + expected = b"\x7fELF" + length = len(expected) + + magic_bytes = descriptor.read(length) + descriptor.seek(0) # reset reading position + + return magic_bytes == expected + +