◐ Shell
reader mode source ↗
Skip to content

gh-142155: Fix infinite recursion in shutil.copytree on Windows junctions#142156

Open
ChuheLin wants to merge 7 commits into
python:mainfrom
ChuheLin:fix-shutil-recursion-loop
Open

gh-142155: Fix infinite recursion in shutil.copytree on Windows junctions#142156
ChuheLin wants to merge 7 commits into
python:mainfrom
ChuheLin:fix-shutil-recursion-loop

Conversation

@ChuheLin

@ChuheLin ChuheLin commented Dec 1, 2025

Copy link
Copy Markdown

Summary

This PR fixes a critical crash (WinError 206 / WinError 1921) in shutil.copytree on Windows when encountering directory junctions that point to a parent directory (recursive cycles).

The Problem

On Windows, shutil.copytree contains specific logic to force traversal into Directory Junctions by treating them as standard directories (is_symlink is forced to False).

However, unlike os.walk or shutil.rmtree (after recent fixes), copytree lacked a cycle detection mechanism for these traversed junctions. This caused infinite recursion until the path exceeded the OS limit, resulting in an unhandled OSError (Crash) rather than a Python RecursionError.

The Fix

I implemented cycle detection using file system identity (st_dev, st_ino), similar to how deepcopy handles recursion:

  1. Added a hidden _seen set parameter to copytree and its helper _copytree.
  2. Before entering a directory, the code now checks if (st.st_dev, st.st_ino) is already in _seen.
  3. If a cycle is detected, it raises a shutil.Error("Infinite recursion detected") immediately, preventing the crash.

Verification

  1. Regression Test: Added test_copytree_recursive_junction to Lib/test/test_shutil.py.
    • Status: PASSED on local Windows build.
  2. Manual Verification: Verified with a reproduction script that previously caused a WinError 206 crash.
    • Before fix: Script crashed after creating a path > 4000 chars.
    • After fix: Script correctly raises shutil.Error: Infinite recursion detected.

@ChuheLin ChuheLin requested a review from giampaolo as a code owner December 1, 2025 17:12
@bedevere-app

bedevere-app Bot commented Dec 1, 2025

Copy link
Copy Markdown

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@ChuheLin ChuheLin changed the title gh-142155: Fix infinite recursion in shutil.copytree on Windows junct… Dec 1, 2025

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide comment

If you want to have a recursive guard, use a frozenset() as a default and update the frozenset every time. We do this in typing.py I think or maybe elsewhere (search for recursive_guard in Lib)

@bedevere-app

bedevere-app Bot commented Dec 3, 2025

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@ChuheLin ChuheLin requested a review from picnixz December 3, 2025 18:45

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide comment

Address all changes before requesting a new review please, either by adding a comment if I was not clear or by changing the code.

@ChuheLin

ChuheLin commented Dec 3, 2025

Copy link
Copy Markdown
Author

Address all changes before requesting a new review please, either by adding a comment if I was not clear or by changing the code.

Thanks for the detailed review! It's getting late in my time zone, so I'm going to get some rest now. I will go through all your comments and update the code first thing tomorrow.

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting changes stale Stale PR or inactive for long period of time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants