From edde2083c6e93e42b979068af5529710b5e2a7ab Mon Sep 17 00:00:00 2001
From: Stig Palmquist <stig@stig.io>
Date: Thu, 21 May 2026 19:59:21 +0100
Subject: [PATCH] Validate symlink and hardlink linkname in SECURE MODE

Archive::Tar before 3.08 passes the tar header's linkname directly to
symlink()/link() in _make_special_file() without validating absolute
paths or '..' segments. This allows a crafted tar archive to create
symlinks or hardlinks targeting paths outside the extraction directory,
leading to arbitrary file read/write.

Add validation in SECURE EXTRACT MODE (the default) to reject:
- Symlink/hardlink targets with absolute paths
- Symlink/hardlink targets containing '..' path traversal

Adjusted patch paths from upstream Archive::Tar standalone repository
(lib/Archive/Tar.pm) to match perl5 source tree layout
(cpan/Archive-Tar/lib/Archive/Tar.pm).

Upstream-Status: Backport [https://github.com/jib/archive-tar-new/commit/17c873492a05eddc0de18c1485e0b2cccd5a9158]

CVE: CVE-2026-42496
CVE: CVE-2026-42497

Signed-off-by: Chris 'BinGOs' Williams <chris@bingosnet.co.uk>
Signed-off-by: Abhishek Bachiphale <Abhishek.Bachiphale@windriver.com>
---
 cpan/Archive-Tar/lib/Archive/Tar.pm     | 30 +++++++++++++++++++++++++
 cpan/Archive-Tar/t/04_resolved_issues.t |  2 ++
 2 files changed, 32 insertions(+)

diff --git a/cpan/Archive-Tar/lib/Archive/Tar.pm b/cpan/Archive-Tar/lib/Archive/Tar.pm
index 2df0931..733feef 100644
--- a/cpan/Archive-Tar/lib/Archive/Tar.pm
+++ b/cpan/Archive-Tar/lib/Archive/Tar.pm
@@ -954,6 +954,19 @@ sub _make_special_file {
     my $err;
 
     if( $entry->is_symlink ) {
+        if( !$INSECURE_EXTRACT_MODE ) {
+            my $linkname = $entry->linkname;
+            if( File::Spec->file_name_is_absolute($linkname) ) {
+                $self->_error( qq[Symlink '] . $entry->full_path .
+                    qq[' has absolute target. Not extracting under SECURE EXTRACT MODE] );
+                return;
+            }
+            if( grep { $_ eq '..' } File::Spec->splitdir($linkname) ) {
+                $self->_error( qq[Symlink '] . $entry->full_path .
+                    qq[' target attempts traversal. Not extracting under SECURE EXTRACT MODE] );
+                return;
+            }
+        }
         my $fail;
         if( ON_UNIX ) {
             symlink( $entry->linkname, $file ) or $fail++;
@@ -967,6 +980,23 @@ sub _make_special_file {
                 $entry->linkname .q[' failed] if $fail;
 
     } elsif ( $entry->is_hardlink ) {
+        if( !$INSECURE_EXTRACT_MODE ) {
+            my $linkname = $entry->linkname;
+            if( File::Spec->file_name_is_absolute($linkname) ) {
+                $self->_error( qq[Hardlink '] . $entry->full_path .
+                    qq[' has absolute target '$linkname'. Not extracting ] .
+                    qq[under SECURE EXTRACT MODE: extraction itself chmods ] .
+                    qq[the shared inode.] );
+                return;
+            }
+            if( grep { $_ eq '..' } File::Spec->splitdir($linkname) ) {
+                $self->_error( qq[Hardlink '] . $entry->full_path .
+                    qq[' target '$linkname' attempts traversal. Not ] .
+                    qq[extracting under SECURE EXTRACT MODE: extraction ] .
+                    qq[itself chmods the shared inode.] );
+                return;
+            }
+        }
         my $fail;
         if( ON_UNIX ) {
             link( $entry->linkname, $file ) or $fail++;
diff --git a/cpan/Archive-Tar/t/04_resolved_issues.t b/cpan/Archive-Tar/t/04_resolved_issues.t
index b3566a1..08d339a 100644
--- a/cpan/Archive-Tar/t/04_resolved_issues.t
+++ b/cpan/Archive-Tar/t/04_resolved_issues.t
@@ -220,6 +220,7 @@ if ($^O ne 'msys') # symlink tests fail on Windows/msys2
 		}
 
     { #use case 1 - in memory extraction
+      local $Archive::Tar::INSECURE_EXTRACT_MODE=1;
 			my $t=Archive::Tar->new;
 			$t->read( $archname );
 			my $r = eval{ $t->extract };
@@ -231,6 +232,7 @@ if ($^O ne 'msys') # symlink tests fail on Windows/msys2
 
 		{ #use case 2 - iter extraction
 		  #$DB::single = 2;
+      local $Archive::Tar::INSECURE_EXTRACT_MODE=1;
 			my $next=Archive::Tar->iter( $archname, 1 );
 			my $failed = 0;
 			#use Data::Dumper;
