Skip to content

gh-160863: Reject links reaching destination via symlinks or .. - #23

Draft
gpshead wants to merge 1 commit into
mainfrom
claude/relaxed-cray-8mejy7
Draft

gpshead wants to merge 1 commit into
mainfrom
claude/relaxed-cray-8mejy7

Conversation

@gpshead

@gpshead gpshead commented Oct 29, 2026

Copy link
Copy Markdown
Owner

Summary

Fix a security vulnerability in tarfile.tar_filter() and tarfile.data_filter() where symlink and hardlink members could reach the destination directory or existing symlinks outside it through .. components or earlier symlink members in the archive.

Changes

Core Fix (Lib/tarfile.py)

Modified the link validation logic in _get_filtered_attrs() to:

  • Judge link members by where they would actually be made, not just their name
  • Resolve only the parent directory path (not the link target itself)
  • Reject links that would be placed at the destination directory itself
  • Reject links that would replace existing symlinks outside the destination

This prevents attackers from using constructs like:

  • ../dest symlinks that reach the destination
  • s/. hardlinks where s is a symlink to ..
  • Links replacing outer symlinks that point into the destination

Tests (Lib/test/test_tarfile.py)

Added three comprehensive test cases:

  • test_link_reaching_destination(): Verifies rejection of links reaching destination via .. or symlink traversal
  • test_link_replacing_outer_symlink(): Ensures links cannot replace existing symlinks outside the destination
  • test_link_to_dot_reextracted(): Confirms symlinks to . can be re-extracted correctly

Documentation (Doc/library/tarfile.rst)

Updated documentation to clarify that:

  • Links are refused if they would be made at the destination or outside it
  • Only parent directories are resolved; links replace rather than follow their target path
  • Added version note about the security fix

Test Plan

All new tests pass with both 'tar' and 'data' filters. Existing tarfile tests continue to pass, confirming backward compatibility for legitimate use cases.

https://claude.ai/code/session_019EeY1aVDfL7Y3eMxYWdYgc

pythongh-160097 gave tar_filter the "link at the destination" check that
data_filter had, but made it lexical (the member's normalised name is
'.'), so link members that reach the destination through '..' or
symlinks passed even filter='data':

* '../dest' now raised RecursionError instead of OutsideDestinationError;
* 's' -> '.' followed by a hard link 's/.' changed the destination
  directory's own mode;
* '../current', where 'current' is an existing symlink outside the
  destination pointing at it, deleted that symlink and wrote later
  members outside the destination.

A link is made at its own path without following whatever is already
there, so check that path with only its parent directory resolved, and
require it to be strictly inside the destination.  This covers all of
the above for both filters, also rejects a link whose parent is reached
through an earlier symlink member leading outside, and avoids the false
positive of the old resolved-path check when re-extracting an archive
containing a symlink to '.'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EeY1aVDfL7Y3eMxYWdYgc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant