August 3, 2025·4 min read·engineeringperlsecuritythreads

Perl 5.40.3 removes a working-directory race in thread cloning

Cloning an open directory handle could temporarily change the process working directory. Perl 5.40.3 repairs that behavior so another thread does not resolve file operations against an unintended directory.

Creating a thread should not temporarily redirect another thread's file operations. Perl's directory-handle cloning could do exactly that, because it changed the process-wide working directory while preparing the new thread's copy. Today's Perl 5.40.3 removes that behavior from the maintenance branch, addressing CVE-2025-40909.

The release notes credit Vincent Lefèvre with discovering the problem and say Perl will no longer change directory to each handle during cloning. The history records date 5.40.3 to August 3. This is a maintenance correction with no intentional incompatibilities with 5.40.2.

The CPANSec advisory published in May explains the concurrency condition. An open directory handle at thread creation causes the process working directory to change temporarily while that handle is cloned. An already-running third thread can observe that change and perform operations against unexpected locations.

I associate Perl with concise text processing, but the interpreter's facilities extend beyond the expression that reads or transforms text. File resolution and thread creation can interact through state the program did not explicitly connect. This correction is useful because it removes an implicit change to that state inside an interpreter operation.

A restored directory can still have been observed

The vulnerability does not require the working directory to remain wrong after thread creation completes. A temporary change is enough if another thread resolves a path during the interval. Restoring the original directory later does not undo an operation that already opened or loaded something elsewhere.

That is the defining timing problem. Code examining the working directory before and after thread creation can see the expected value both times. A concurrent operation can still have used a different value between those observations. A final-state check therefore cannot establish that every operation during the interval resolved its paths correctly.

The advisory identifies unintended code loading and file access as possible consequences. Those consequences follow from interpreting a path under an unexpected current directory. They do not require the path string itself to change. The same string can identify a different file when the shared directory state changes underneath its resolution.

This also explains why the condition involves an already-running thread. The newly created thread is not the only participant in the cloning operation's consequences. An unrelated thread can continue executing while the interpreter temporarily changes shared process state. The cloning implementation has created an observable condition outside the new thread's own interpreter state.

Directory handles introduced a hidden process operation

An open directory handle identifies a directory being examined. Cloning that handle needs an implementation strategy, but changing the process working directory is a much broader action than duplicating bookkeeping for one handle. The vulnerability comes from using that broader action during a concurrent operation.

The upstream patch series describes replacing the fchdir approach with fdopendir and dup. Its explanation also identifies a tradeoff. Handles created through the replacement approach share descriptor position among threads, unlike the assumptions made by the previous cloning tests.

That is an important limit on a simplistic description of the fix. Eliminating the process-wide directory change does not mean every incidental property of the previous handle implementation remains identical. The patch series removes tests that assumed an entirely separate cloned directory iterator, because those assumptions belonged to the old implementation.

The release notes point to two upstream changes supplying the fixes. For an operator, the maintenance release is the relevant packaged correction. For someone maintaining code that shares directory handles across threads, the upstream discussion explains why iterator behavior deserves a more precise reading than merely assuming cloning means independent traversal state.

Relative operations inherit shared directory state

The advisory's reference to unintended paths is broader than an explicit application call to chdir. A program can avoid changing directory itself and still encounter the interpreter's temporary change. The vulnerable operation is hidden inside thread cloning, so searching application source only for directory-changing calls would miss the cause.

Relative file access is particularly dependent on the directory used for resolution. If an operation supplies a relative path, some surrounding state must establish its starting directory. In this defect, another thread's interpreter activity can alter that state at the moment the operation runs. The application expression may look unchanged while its target differs.

The security implication depends on what can exist at the unexpected location and which operation resolves there. CPANSec says a local attacker may be able to exploit the behavior. That qualification should remain part of the description. The advisory does not establish that every threaded program permits attacker-controlled code loading merely because it has an open directory handle.

Removing the implicit directory change addresses the implementation cause. Application conventions about paths can reduce particular exposures, but they do not supply an interpreter-level guarantee about shared process state. The maintenance correction gives existing callers a version in which the cloning operation no longer performs that directory change.

The supported branch receives the earlier correction

The advisory was public before today's maintenance release. Its existence and the August release are separate events, so August's change should be described as delivery of the fix on the 5.40 maintenance line. The release notes explicitly connect the packaged correction to the same identifier and upstream reports. That connection lets a reader relate the installed interpreter package to the published vulnerability description.

For me, this is a reason to distinguish a language feature upgrade from interpreter maintenance. A program does not need new syntax to benefit from corrected file-resolution behavior during thread creation. The relevant change is the removal of an internal process operation that could affect an already-running thread.

Perl 5.40.3 keeps the maintenance branch's stated compatibility intent while repairing that concurrency failure. The resulting guarantee is specific. Cloning an open directory handle no longer changes the process working directory to that handle, preventing unrelated file operations from observing that temporary location through this path.