DISCLAIMER: Image is generated using Leonardo AI
.
Introduction
Step 1: Dockerfile
Step 2: Docker Compose
Step 3: Test Script
Step 4: Build & Test
Conclusion
Introduction
In this post, we demonstrate how to reproduce CVE-2025-40909
, a vulnerability in Perl
related to working directories and thread behaviour.
We are using Docker
to isolate the environment and control the Perl
version.
This CVE
addresses a bug in Perl v5.40.0
and earlier where thread local working directory state is mishandled.
Specifically, calling getcwd()
inside a thread after chdir()
can yield incorrect results or return undef.
This breaks assumption about filesystem state and can lead to bugs or security issues in threaded Perl
applications.
Source: Perl v5.40.3 Delta
CPAN Security Group: CVE-2025-40909
Step 1: Dockerfile
We will build Perl v5.34.0
that is known to be vulnerable.
Here we build from source with thread
support.
File: Dockerfile
FROM debian:buster
ENV PERL_VERSION=5.34.0
ENV PREFIX=/usr/local/perl-${PERL_VERSION}
RUN sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list && \
sed -i '/security.debian.org/d' /etc/apt/sources.list && \
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-check-valid-until && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
build-essential \
wget \
libssl-dev \
zlib1g-dev \
libperl-dev \
make && \
rm -rf /var/lib/apt/lists/*
RUN wget https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz && \
tar xzf perl-${PERL_VERSION}.tar.gz && \
cd perl-${PERL_VERSION} && \
./Configure -des -Dprefix=${PREFIX} -Dusethreads && \
make -j"$(nproc)" && \
make install
ENV PATH="${PREFIX}/bin:${PATH}"
CMD ["perl", "/test_cve.pl"]
Step 2: Docker Compose
Here is docker compose configuration using the above Dockerfile
to build and run the custom image.
File: docker-compose.yml
services:
perl-cve-check:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./test_cve.pl:/test_cve.pl:ro
command: perl /test_cve.pl
Step 3: Test Script
Now we have script, test_cve.pl
, to demonstrate the vulnerability.
use strict;
use warnings;
use Cwd qw(getcwd);
use threads;
sub thread_func {
# This returns "/tmp" before chdir("/tmp")
my $before = getcwd() // '[getcwd failed]';
chdir("/tmp") or die "chdir failed: $!";
# This returns undef after chdir("/tmp")
my $after = getcwd() // '[getcwd failed]';
return ($before, $after);
}
my $main_dir = getcwd() // '[getcwd failed]';
my $thr = threads->create(\&thread_func);
my ($orig_dir, $thread_dir) = $thr->join();
my $vulnerable = (!defined($thread_dir) || $thread_dir eq '' || $orig_dir ne "/");
print $vulnerable
? "[!] Vulnerable to CVE-2025-40909: getcwd changed inside thread\n"
: "[+] Not vulnerable to CVE-2025-40909\n";
print "Main thread cwd: $main_dir\n";
print "Thread original cwd (before chdir): $orig_dir\n";
print "Thread cwd (after chdir): ", ($thread_dir // '[undef]'), "\n";
exit($vulnerable ? 29 : 0);
Step 4: Build & Test
$ docker compose up --build
[+] Building 0.9s (10/10) FINISHED
...
...
...
perl-cve-check-1 | [!] Vulnerable to CVE-2025-40909: getcwd changed inside thread
perl-cve-check-1 | Main thread cwd: /
perl-cve-check-1 | Thread original cwd (before chdir): /tmp
perl-cve-check-1 | Thread cwd (after chdir): [undef]
perl-cve-check-1 exited with code 29
Conclusion
We’ve now reproduced CVE-2025-40909
in a controlled Docker
environment.
This demonstrates how Perl
threads can mismanage working directories in vulnerable versions.
Happy Hacking !!!