Validate tar link targets in data_filter fallback (GHSA-p4qx-p8p6-4gjf) - #6654
Conversation
The ``LinkOutsideDestinationError`` fallback in ``untar_file`` routed through ``tarfile.tar_filter``, which does not perform link-target containment checks (those live inside the ``for_data`` branch of CPython's ``_get_filtered_attrs``). On the three CPython patch versions the workaround targets, attacker-controlled hardlink/symlink members whose targets escaped the destination directory were silently allowed, permitting writes outside the install root. The fallback now only runs when an independent containment check confirms the link target stays inside the destination — otherwise it fails closed and the extraction is aborted. The check is also captured in tasks/vendoring/patches/patched/ so it survives a re-vendor of pip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses GHSA-p4qx-p8p6-4gjf by ensuring that when tarfile.data_filter triggers the historical LinkOutsideDestinationError fallback on specific buggy CPython patch versions, link targets are independently validated to remain within the extraction destination before falling back to tarfile.tar_filter.
Changes:
- Add
_tar_link_target_is_within()to re-check hardlink/symlink containment prior to using the permissivetar_filterfallback. - Tighten the
LinkOutsideDestinationErrorfallback logic to only allow fallback on affected CPython patch versions and only for in-bounds link targets; otherwise fail closed. - Add a vendoring patch to preserve the fix across pip re-vendoring, plus new unit/regression tests for the helper and end-to-end extraction behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
pipenv/patched/pip/_internal/utils/unpacking.py |
Adds link-target containment helper and gates the buggy-CPython tar_filter fallback on the helper returning in-bounds. |
tests/unit/test_tar_link_safety.py |
Introduces regression tests for helper behavior and extraction behavior under simulated buggy data_filter. |
tasks/vendoring/patches/patched/pip_tarfile_link_safety.patch |
Mirrors the unpacking.py changes as a vendoring patch to make the fix durable across updates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…sertions Agent-Logs-Url: https://github.com/pypa/pipenv/sessions/e5e508a6-9ae2-4384-8634-fc86125bbb84 Co-authored-by: matteius <479892+matteius@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Fixes GHSA-p4qx-p8p6-4gjf: a tar hardlink/symlink traversal in
pipenv.patched.pip._internal.utils.unpacking.untar_file.When
tarfile.data_filterraisedLinkOutsideDestinationErrorfor a link member,untar_filefell back totarfile.tar_filteron three specific CPython patch versions (3.9.17 / 3.10.12 / 3.11.4) that shipped with python/cpython#107845. The link-target containment check in CPython's_get_filtered_attrslives inside theif for_data:branch, sotar_filterperforms no containment validation. An attacker-controlled tarball with a hardlink whoselinknameescapes the destination (e.g.../../etc-passwd-shadow) would have its link materialized outside the install root, enabling cache pollution / overwrites of files outside the destination._tar_link_target_is_within(member, destination)re-implements the containment checkdata_filteris supposed to perform — resolves the link target relative to the destination (or, for symlinks, relative to the link's own directory), then confirms it stays inside viaos.path.commonpath.pip_filternow only routes totar_filterwhen (a) we're on a buggy CPython patch version and (b) the link target is independently confirmed in-bounds. Otherwise the originalLinkOutsideDestinationErroris re-raised anduntar_filewraps it inInstallationError, so the install fails closed.tasks/vendoring/patches/patched/pip_tarfile_link_safety.patchso it survives a re-vendor of pip.Test plan
tests/unit/test_tar_link_safety.py(11 tests):_tar_link_target_is_within— accepts in-bounds hardlinks/symlinks; rejects../traversal, deep../../../traversal, absolute paths, empty linknames, and non-link members.data_filterto simulate the buggy CPython behaviour: malicious hardlink rejected (no link materialised on disk,InstallationErrorraised); legitimate in-bounds hardlink still extracts; modern-CPython path also rejects malicious hardlink before the fallback runs.python -m pytest tests/unit -q— 434 passed, 0 new failures (one pre-existing unrelated failure onmain).ruff checkclean on the touched files.git apply --checkconfirmedpip_tarfile_link_safety.patchis byte-identical to the working-tree diff.🤖 Generated with Claude Code