fix(linter): skip foreign-arch ELF files in library linter - #6249
Open
lengau wants to merge 1 commit into
Open
Conversation
The library linter calls load_dependencies() on every ELF file found in the prime directory. For foreign-arch builds (e.g. arm64 snap on an amd64 host) this invokes QEMU via binfmt to run the foreign binary, which fails with 'not a dynamic executable' errors and can SEGFAULT, producing multi-gigabyte core dumps. Add elf_machine field to _ArchConfig and get_host_elf_machine() to elf_utils.py. In library_linter.run(), skip any ELF file whose arch_tuple[2] (e_machine) does not match the host's expected value. Fixes: #4373 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a bug where snapcraft's library linter invokes foreign-arch ELF binaries (via binfmt/QEMU) when building cross-arch snaps, causing spurious errors and large core dumps. The linter now compares each ELF's e_machine against the host's value and skips foreign binaries.
Changes:
- Added
elf_machineto_ArchConfigand a newget_host_elf_machine()helper insnapcraft/elf/elf_utils.py. - Added a foreign-arch skip in
LibraryLinter.run()based onelf_file.arch_tuple[2]. - Added unit and integration tests; unrelated
FakeProvidermethods (list_instances,prune) added totests/unit/conftest.py.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| snapcraft/elf/elf_utils.py | Adds elf_machine field to _ArchConfig and get_host_elf_machine() helper. |
| snapcraft/linters/library_linter.py | Skips ELF files whose e_machine doesn't match the host's. |
| tests/unit/linters/test_library_linter.py | Adds arch_tuple to mocks and a new unit test for the skip behavior. |
| tests/integration/linters/test_library_linter.py | New integration test that downloads a powerpc bash .deb to verify the skip. |
| tests/unit/conftest.py | Unrelated additions of list_instances/prune to FakeProvider. |
Comments suppressed due to low confidence (1)
snapcraft/linters/library_linter.py:81
elf_file.arch_tuplecan beNone(seesnapcraft/elf/_elf_file.py:198, where it's initialised toNoneand only populated when the ELF header is successfully parsed). Treatingarch_tuple is Noneas "do not skip" means a file whose architecture could not be determined will still be passed toload_dependencies(), which immediately raisesRuntimeError("failed to parse architecture")(see_elf_file.py:390-391). Consider skipping these files as well, rather than letting them through.
if (
host_elf_machine is not None
and elf_file.arch_tuple is not None
and elf_file.arch_tuple[2] != host_elf_machine
):
emit.debug(
f"Skipping library linting for foreign-arch ELF "
f"{str(elf_file.path)!r} ({elf_file.arch_tuple[2]})"
)
continue
Comment on lines
+30
to
+61
| _POWERPC_BASH_URL = "https://old-releases.ubuntu.com/ubuntu/pool/main/b/bash/bash_2.05b-15ubuntu5_powerpc.deb" | ||
|
|
||
|
|
||
| def setup_function(): | ||
| elf_utils.get_elf_files.cache_clear() | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def powerpc_bash(tmp_path): | ||
| """Download and extract the bash binary from a powerpc .deb package. | ||
|
|
||
| Returns the path to the extracted bash executable, which is a 32-bit | ||
| big-endian PowerPC ELF (EM_PPC) — foreign on every supported host arch. | ||
| """ | ||
| deb_path = tmp_path / "bash-powerpc.deb" | ||
| extract_dir = tmp_path / "bash-powerpc" | ||
|
|
||
| try: | ||
| urllib.request.urlretrieve(_POWERPC_BASH_URL, deb_path) # noqa: S310 | ||
| except OSError as exc: | ||
| pytest.skip(f"Could not download powerpc bash package: {exc}") | ||
|
|
||
| extract_dir.mkdir() | ||
| result = subprocess.run( | ||
| ["dpkg-deb", "-x", str(deb_path), str(extract_dir)], | ||
| capture_output=True, | ||
| check=False, | ||
| ) | ||
| if result.returncode != 0: | ||
| pytest.skip(f"Could not extract powerpc bash package: {result.stderr.decode()}") | ||
|
|
||
| return extract_dir / "bin" / "bash" |
| @@ -0,0 +1,88 @@ | |||
| # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- | |||
| # | |||
| # Copyright 2024 Canonical Ltd. | |||
Comment on lines
+72
to
+81
| if ( | ||
| host_elf_machine is not None | ||
| and elf_file.arch_tuple is not None | ||
| and elf_file.arch_tuple[2] != host_elf_machine | ||
| ): | ||
| emit.debug( | ||
| f"Skipping library linting for foreign-arch ELF " | ||
| f"{str(elf_file.path)!r} ({elf_file.arch_tuple[2]})" | ||
| ) | ||
| continue |
Comment on lines
+418
to
+428
| def list_instances( | ||
| self, | ||
| *, | ||
| project_name: str | None = None, | ||
| instance_name_prefix: str | None = None, | ||
| include_base_instances: bool = False, | ||
| ): | ||
| return [] | ||
|
|
||
| def prune(self, *, project_name: str, prune_templates: bool = False) -> None: | ||
| pass |
| _POWERPC_BASH_URL = "https://old-releases.ubuntu.com/ubuntu/pool/main/b/bash/bash_2.05b-15ubuntu5_powerpc.deb" | ||
|
|
||
|
|
||
| def setup_function(): |
Comment on lines
+70
to
+79
| # Skip ELF files for foreign architectures — invoking them via binfmt/QEMU | ||
| # to resolve dependencies causes spurious errors and potential SEGFAULTs. | ||
| if ( | ||
| host_elf_machine is not None | ||
| and elf_file.arch_tuple is not None | ||
| and elf_file.arch_tuple[2] != host_elf_machine | ||
| ): | ||
| emit.debug( | ||
| f"Skipping library linting for foreign-arch ELF " | ||
| f"{str(elf_file.path)!r} ({elf_file.arch_tuple[2]})" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The library linter calls load_dependencies() on every ELF file found in the prime directory. For foreign-arch builds (e.g. arm64 snap on an amd64 host) this invokes QEMU via binfmt to run the foreign binary, which fails with 'not a dynamic executable' errors and can SEGFAULT, producing multi-gigabyte core dumps.
Add elf_machine field to _ArchConfig and get_host_elf_machine() to elf_utils.py. In library_linter.run(), skip any ELF file whose arch_tuple[2] (e_machine) does not match the host's expected value.
Fixes: #4373
make lint && make test.