[RFC] libalpm: validate archive entry paths in extract_single_file
Hi all, I'd like to introduce a small hardening change (found by Claude) in lib/libalpm/add.c and get upstream's take before sending an MR. Branch (2 signed commits, rebased on current master): https://gitlab.archlinux.org/strykar/pacman/-/tree/harden/extract-entry-vali... perform_extraction() passes ARCHIVE_EXTRACT_OWNER | PERM | TIME | UNLINK | XATTR | SECURE_SYMLINKS to libarchive (add.c:118) — no SECURE_NODOTDOT, no SECURE_NOABSOLUTEPATHS. extract_single_file then builds the on-disk path as snprintf("%s%s", handle->root, entryname) (add.c:216) from the archive's entryname and overrides the entry pathname via archive_entry_set_pathname. An entryname with a leading /, or one whose .. components leave handle->root after concatenation (e.g. foo/../../etc/evil), extracts outside handle->root. One accidental mitigation to note: entries starting with . route to extract_db_file, which silently drops anything other than .INSTALL/.MTREE/.CHANGELOG (add.c:204-208). So a literal ../etc/x is a no-op today — the practical exploit requires either a non-dot-prefixed traversal (foo/../../etc/x) or a leading /. The alpm_filelist_contains gate at add.c:208 does not provide independent validation: per be_package.c:612-618, newpkg->files is populated from the archive's own .MTREE (preferred) or from the tar entries as fallback. In the unsigned / -U / SigLevel = TrustAll case, both sources are attacker-controlled in a tampered archive, so a malicious builder lists the traversal path in .MTREE and ships the matching tar entry — the check passes. Signed repo install: package is trusted, .INSTALL runs as root, traversal adds nothing. Not trying to escalate this. Where the gap matters. pacman -U on locally-built or SigLevel = TrustAll packages; --root for staging / image builds / pacstrap-like flows (the 2024 Trail of Bits audit, Recommendations p.7, notes --root does not guarantee files outside the root remain untouched); non-root libalpm consumers. Why libarchive flags alone don't cut it. Adding SECURE_NOABSOLUTEPATHS to archive_flags rejects every entry, because pacman itself sets the entry pathname to handle->root + entryname, which is absolute. SECURE_NODOTDOT would catch middle-.. cases (the concat is a dumb snprintf, not normalizing, so .. survives in the final path) but not a leading / in the entry (concat yields //etc/foo, which has no ..). So any fix needs a lexical check on the raw entryname anyway. I dropped SECURE_NODOTDOT as redundant; happy to add it as belt-and-braces if reviewers prefer. Proposed change. A small lexical validator on archive_entry_pathname and archive_entry_hardlink, called before alpm_filelist_contains. Reject on leading /, empty/NULL, or any .. path component. On reject: log and abort the install. Build/test. Builds clean on master. Full meson test passes: 341 OK, 6 pre-existing ExpectedFail, 0 failures — including three new tests covering foo/../../etc/evil (rejected), /etc/evil (rejected), and usr/share/foo..bar/baz (accepted, false-positive guard). Tests assert PACMAN_RETCODE=1 and !FILE_EXIST=etc/evil. One minor test-infra addition: an allow_unsafe_paths flag on pmpkg so absolute-path entries can be authored into a test archive (pmpkg.py:201 refuses them by default). Empirical check. Scanned every file entry in the synced core/extra/multilib/aurpkgs file databases on my machine — 7,712,742 entries, zero with leading / or .. components. The list will have broader visibility than I do; I'd welcome counter-examples from cross / dev-kit packaging in particular. Open questions: Abort vs skip-and-warn? I lean abort: presence is evidence of tampering or a broken build tool. Any legitimate case in Arch packaging where a tar entry has .. or leading /? Follow-up patch to extend to extract_db_file for symmetry? Interest in also enabling SECURE_NODOTDOT as defense-in-depth alongside the lexical check? Orthogonal observation worth a separate thread: when -U extraction fails mid-transaction, the package is left registered in the local db (discovered while writing these tests — not caused by this patch). Happy to file separately. Not framing this as a vulnerability — the declared trust model covers default installs. But the --root / unsigned--U / non-root-consumer gap is the shape of thing the ToB audit flagged as under-specified, and this is ~20 lines that closes it. Commits are signed with key E4A8486E91D492EA. Please let me know if an MR is preferred over this RFC flow or is this a umad / wontfix trustmodel? Thanks, Strykar
On 14/04/2026 3:00 pm, Strykar wrote:
Please let me know if an MR is preferred over this RFC flow or is this a umad / wontfix trustmodel?
MRs are confirmed. Things on mailing lists get lost.
On Tue, 2026-04-14 at 15:41 +1000, Allan McRae wrote:
On 14/04/2026 3:00 pm, Strykar wrote:
Please let me know if an MR is preferred over this RFC flow or is this a umad / wontfix trustmodel?
MRs are confirmed. Things on mailing lists get lost.
MR created - https://gitlab.archlinux.org/pacman/pacman/-/merge_requests/357
participants (2)
-
Allan McRae
-
Strykar