From 3e48f9627fbba4dae5de35be1f735cdeb7e47fb8 Mon Sep 17 00:00:00 2001
From: Shlomi Fish <shlomif@shlomifish.org>
Date: Mon, 8 Jun 2026 18:40:35 +0300
Subject: [PATCH] CVE-2026-11527

---
 config-inifiles/lib/Config/IniFiles.pm |  5 +-
 config-inifiles/t/38security-open.t    | 66 ++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 2 deletions(-)
 create mode 100644 config-inifiles/t/38security-open.t

Index: Config-IniFiles-3.000003/lib/Config/IniFiles.pm
===================================================================
--- Config-IniFiles-3.000003.orig/lib/Config/IniFiles.pm
+++ Config-IniFiles-3.000003/lib/Config/IniFiles.pm
@@ -2158,9 +2158,10 @@ sub _make_filehandle
     my $fh = qualify_to_ref( $thing, caller(1) );
     return $fh if defined( fileno $fh );
 
-    # otherwise treat it as a file to open
+    # otherwise treat it as a file to open; 3-arg open so the filename is
+    # not interpreted as a command or redirect
     $fh = gensym;
-    open( $fh, $thing ) || return;
+    open( $fh, '<', $thing ) || return;
 
     return $fh;
 }    # end _make_filehandle
Index: Config-IniFiles-3.000003/t/38security-open.t
===================================================================
--- /dev/null
+++ Config-IniFiles-3.000003/t/38security-open.t
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+# Regression test for the 2-arg open() in _make_filehandle.
+#
+# _make_filehandle is the open path behind the -file argument (new -> ReadConfig
+# and WriteConfig both reach it). A 2-arg open() there interprets shell-magic
+# prefixes, so a "cmd |" filename runs a command and a "> file" filename
+# truncates a file. These must be treated as plain pathnames.
+
+use strict;
+use warnings;
+
+use Config::IniFiles;
+use File::Temp qw( tempdir );
+use File::Spec;
+use Test::More tests => 5;
+
+my $dir = tempdir( CLEANUP => 1 );
+
+# A trailing-pipe payload must not run a command.
+{
+    my $marker = File::Spec->catfile( $dir, "pwned_read" );
+    my $fh     = eval { Config::IniFiles->_make_filehandle("touch $marker |") };
+    close $fh if $fh;
+    ok !-e $marker, "trailing-pipe payload does not execute a command";
+}
+
+# A leading-pipe payload must not run a command.
+{
+    my $marker = File::Spec->catfile( $dir, "pwned_write" );
+    my $fh     = eval { Config::IniFiles->_make_filehandle("| touch $marker") };
+    close $fh if $fh;
+    ok !-e $marker, "leading-pipe payload does not execute a command";
+}
+
+# A redirect payload must not truncate a file.
+{
+    my $victim = File::Spec->catfile( $dir, "victim" );
+    open my $fh, ">", $victim or die "$victim: $!";
+    print $fh "important data\n";
+    close $fh;
+    my $made = eval { Config::IniFiles->_make_filehandle("> $victim") };
+    close $made if $made;
+    is -s $victim, 15, "redirect payload does not truncate a file";
+}
+
+# A plain filename still opens as a file.
+{
+    my $real = File::Spec->catfile( $dir, "real.txt" );
+    open my $fh, ">", $real or die "$real: $!";
+    print $fh "x\n";
+    close $fh;
+    my $opened = eval { Config::IniFiles->_make_filehandle($real) };
+    ok $opened, "plain filename still opens as a file";
+}
+
+# 2-arg open() silently trimmed surrounding whitespace (including a trailing
+# newline); 3-arg open treats the argument literally, so an un-chomped name no
+# longer opens the trimmed file.
+{
+    my $real = File::Spec->catfile( $dir, "plain.txt" );
+    open my $fh, ">", $real or die "$real: $!";
+    print $fh "x\n";
+    close $fh;
+    my $padded = eval { Config::IniFiles->_make_filehandle("$real\n") };
+    ok !$padded, "trailing whitespace is significant (filename not trimmed)";
+}
