From 3e48f9627fbba4dae5de35be1f735cdeb7e47fb8 Mon Sep 17 00:00:00 2001 From: Shlomi Fish 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 diff --git a/config-inifiles/lib/Config/IniFiles.pm b/config-inifiles/lib/Config/IniFiles.pm index 3643909..279da95 100644 --- a/config-inifiles/lib/Config/IniFiles.pm +++ b/config-inifiles/lib/Config/IniFiles.pm @@ -2967,9 +2967,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 diff --git a/config-inifiles/t/38security-open.t b/config-inifiles/t/38security-open.t new file mode 100644 index 0000000..6891926 --- /dev/null +++ b/config-inifiles/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)"; +}