CVE-2025-40909

Thursday, Aug 7, 2025| Tags: perl

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 !!!

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me