dcb3b7
From 9a4826e0881f8c5498a0fd5f24ed2a0fefb771b7 Mon Sep 17 00:00:00 2001
dcb3b7
From: Tony Cook <tony@develop-help.com>
dcb3b7
Date: Thu, 2 Nov 2017 20:18:56 +0000
dcb3b7
Subject: [PATCH] (perl #131895) fail stat on names with \0 embedded
dcb3b7
MIME-Version: 1.0
dcb3b7
Content-Type: text/plain; charset=UTF-8
dcb3b7
Content-Transfer-Encoding: 8bit
dcb3b7
dcb3b7
Also lstat() and the file test ops.
dcb3b7
dcb3b7
Petr Písař: Port to 5.24.3.
dcb3b7
dcb3b7
Signed-off-by: Petr Písař <ppisar@redhat.com>
dcb3b7
---
dcb3b7
 doio.c                | 21 ++++++++++++++++-----
dcb3b7
 pp_sys.c              | 29 +++++++++++++++++++++++------
dcb3b7
 t/lib/warnings/pp_sys | 14 ++++++++++++++
dcb3b7
 t/op/filetest.t       | 10 +++++++++-
dcb3b7
 t/op/stat.t           | 12 +++++++++++-
dcb3b7
 5 files changed, 73 insertions(+), 13 deletions(-)
dcb3b7
dcb3b7
diff --git a/doio.c b/doio.c
dcb3b7
index 6704862..2792c66 100644
dcb3b7
--- a/doio.c
dcb3b7
+++ b/doio.c
dcb3b7
@@ -1458,7 +1458,7 @@ Perl_my_stat_flags(pTHX_ const U32 flags)
dcb3b7
 	return PL_laststatval;
dcb3b7
     else {
dcb3b7
 	SV* const sv = TOPs;
dcb3b7
-	const char *s;
dcb3b7
+	const char *s, *d;
dcb3b7
 	STRLEN len;
dcb3b7
 	if ((gv = MAYBE_DEREF_GV_flags(sv,flags))) {
dcb3b7
 	    goto do_fstat;
dcb3b7
@@ -1472,9 +1472,14 @@ Perl_my_stat_flags(pTHX_ const U32 flags)
dcb3b7
 	s = SvPV_flags_const(sv, len, flags);
dcb3b7
 	PL_statgv = NULL;
dcb3b7
 	sv_setpvn(PL_statname, s, len);
dcb3b7
-	s = SvPVX_const(PL_statname);		/* s now NUL-terminated */
dcb3b7
+	d = SvPVX_const(PL_statname);		/* s now NUL-terminated */
dcb3b7
 	PL_laststype = OP_STAT;
dcb3b7
-	PL_laststatval = PerlLIO_stat(s, &PL_statcache);
dcb3b7
+        if (!IS_SAFE_PATHNAME(s, len, OP_NAME(PL_op))) {
dcb3b7
+            PL_laststatval = -1;
dcb3b7
+        }
dcb3b7
+        else {
dcb3b7
+            PL_laststatval = PerlLIO_stat(d, &PL_statcache);
dcb3b7
+        }
dcb3b7
 	if (PL_laststatval < 0 && ckWARN(WARN_NEWLINE) && should_warn_nl(s)) {
dcb3b7
             GCC_DIAG_IGNORE(-Wformat-nonliteral); /* PL_warn_nl is constant */
dcb3b7
 	    Perl_warner(aTHX_ packWARN(WARN_NEWLINE), PL_warn_nl, "stat");
dcb3b7
@@ -1491,6 +1496,7 @@ Perl_my_lstat_flags(pTHX_ const U32 flags)
dcb3b7
     static const char* const no_prev_lstat = "The stat preceding -l _ wasn't an lstat";
dcb3b7
     dSP;
dcb3b7
     const char *file;
dcb3b7
+    STRLEN len;
dcb3b7
     SV* const sv = TOPs;
dcb3b7
     bool isio = FALSE;
dcb3b7
     if (PL_op->op_flags & OPf_REF) {
dcb3b7
@@ -1534,9 +1540,14 @@ Perl_my_lstat_flags(pTHX_ const U32 flags)
dcb3b7
                               HEKfARG(GvENAME_HEK((const GV *)
dcb3b7
                                           (SvROK(sv) ? SvRV(sv) : sv))));
dcb3b7
     }
dcb3b7
-    file = SvPV_flags_const_nolen(sv, flags);
dcb3b7
+    file = SvPV_flags_const(sv, len, flags);
dcb3b7
     sv_setpv(PL_statname,file);
dcb3b7
-    PL_laststatval = PerlLIO_lstat(file,&PL_statcache);
dcb3b7
+    if (!IS_SAFE_PATHNAME(file, len, OP_NAME(PL_op))) {
dcb3b7
+        PL_laststatval = -1;
dcb3b7
+    }
dcb3b7
+    else {
dcb3b7
+        PL_laststatval = PerlLIO_lstat(file,&PL_statcache);
dcb3b7
+    }
dcb3b7
     if (PL_laststatval < 0 && ckWARN(WARN_NEWLINE) && should_warn_nl(file)) {
dcb3b7
         GCC_DIAG_IGNORE(-Wformat-nonliteral); /* PL_warn_nl is constant */
dcb3b7
         Perl_warner(aTHX_ packWARN(WARN_NEWLINE), PL_warn_nl, "lstat");
dcb3b7
diff --git a/pp_sys.c b/pp_sys.c
dcb3b7
index bd55043..1a72e60 100644
dcb3b7
--- a/pp_sys.c
dcb3b7
+++ b/pp_sys.c
dcb3b7
@@ -2927,19 +2927,24 @@ PP(pp_stat)
dcb3b7
     }
dcb3b7
     else {
dcb3b7
         const char *file;
dcb3b7
+        const char *temp;
dcb3b7
+        STRLEN len;
dcb3b7
 	if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVIO) { 
dcb3b7
             io = MUTABLE_IO(SvRV(sv));
dcb3b7
             if (PL_op->op_type == OP_LSTAT)
dcb3b7
                 goto do_fstat_warning_check;
dcb3b7
             goto do_fstat_have_io; 
dcb3b7
         }
dcb3b7
-        
dcb3b7
 	SvTAINTED_off(PL_statname); /* previous tainting irrelevant */
dcb3b7
-	sv_setpv(PL_statname, SvPV_nomg_const_nolen(sv));
dcb3b7
+        temp = SvPV_nomg_const(sv, len);
dcb3b7
+	sv_setpv(PL_statname, temp);
dcb3b7
 	PL_statgv = NULL;
dcb3b7
 	PL_laststype = PL_op->op_type;
dcb3b7
         file = SvPV_nolen_const(PL_statname);
dcb3b7
-	if (PL_op->op_type == OP_LSTAT)
dcb3b7
+        if (!IS_SAFE_PATHNAME(temp, len, OP_NAME(PL_op))) {
dcb3b7
+            PL_laststatval = -1;
dcb3b7
+        }
dcb3b7
+	else if (PL_op->op_type == OP_LSTAT)
dcb3b7
 	    PL_laststatval = PerlLIO_lstat(file, &PL_statcache);
dcb3b7
 	else
dcb3b7
 	    PL_laststatval = PerlLIO_stat(file, &PL_statcache);
dcb3b7
@@ -3175,8 +3180,12 @@ PP(pp_ftrread)
dcb3b7
 
dcb3b7
     if (use_access) {
dcb3b7
 #if defined(HAS_ACCESS) || defined (PERL_EFF_ACCESS)
dcb3b7
-	const char *name = SvPV_nolen(*PL_stack_sp);
dcb3b7
-	if (effective) {
dcb3b7
+        STRLEN len;
dcb3b7
+	const char *name = SvPV(*PL_stack_sp, len);
dcb3b7
+        if (!IS_SAFE_PATHNAME(name, len, OP_NAME(PL_op))) {
dcb3b7
+            result = -1;
dcb3b7
+        }
dcb3b7
+	else if (effective) {
dcb3b7
 #  ifdef PERL_EFF_ACCESS
dcb3b7
 	    result = PERL_EFF_ACCESS(name, access_mode);
dcb3b7
 #  else
dcb3b7
@@ -3501,10 +3510,18 @@ PP(pp_fttext)
dcb3b7
     }
dcb3b7
     else {
dcb3b7
         const char *file;
dcb3b7
+        const char *temp;
dcb3b7
+        STRLEN temp_len;
dcb3b7
         int fd; 
dcb3b7
 
dcb3b7
         assert(sv);
dcb3b7
-	sv_setpv(PL_statname, SvPV_nomg_const_nolen(sv));
dcb3b7
+        temp = SvPV_nomg_const(sv, temp_len);
dcb3b7
+	sv_setpv(PL_statname, temp);
dcb3b7
+        if (!IS_SAFE_PATHNAME(temp, temp_len, OP_NAME(PL_op))) {
dcb3b7
+            PL_laststatval = -1;
dcb3b7
+            PL_laststype = OP_STAT;
dcb3b7
+            FT_RETURNUNDEF;
dcb3b7
+        }
dcb3b7
       really_filename:
dcb3b7
         file = SvPVX_const(PL_statname);
dcb3b7
 	PL_statgv = NULL;
dcb3b7
diff --git a/t/lib/warnings/pp_sys b/t/lib/warnings/pp_sys
dcb3b7
index 6338964..ded5d7d 100644
dcb3b7
--- a/t/lib/warnings/pp_sys
dcb3b7
+++ b/t/lib/warnings/pp_sys
dcb3b7
@@ -962,3 +962,17 @@ close $fh;
dcb3b7
 unlink $file;
dcb3b7
 EXPECT
dcb3b7
 syswrite() is deprecated on :utf8 handles at - line 6.
dcb3b7
+########
dcb3b7
+# NAME stat on name with \0
dcb3b7
+use warnings;
dcb3b7
+my @x = stat("./\0-");
dcb3b7
+my @y = lstat("./\0-");
dcb3b7
+-T ".\0-";
dcb3b7
+-x ".\0-";
dcb3b7
+-l ".\0-";
dcb3b7
+EXPECT
dcb3b7
+Invalid \0 character in pathname for stat: ./\0- at - line 2.
dcb3b7
+Invalid \0 character in pathname for lstat: ./\0- at - line 3.
dcb3b7
+Invalid \0 character in pathname for fttext: .\0- at - line 4.
dcb3b7
+Invalid \0 character in pathname for fteexec: .\0- at - line 5.
dcb3b7
+Invalid \0 character in pathname for ftlink: .\0- at - line 6.
dcb3b7
diff --git a/t/op/filetest.t b/t/op/filetest.t
dcb3b7
index 8883381..bd1d08c 100644
dcb3b7
--- a/t/op/filetest.t
dcb3b7
+++ b/t/op/filetest.t
dcb3b7
@@ -9,7 +9,7 @@ BEGIN {
dcb3b7
     set_up_inc(qw '../lib ../cpan/Perl-OSType/lib');
dcb3b7
 }
dcb3b7
 
dcb3b7
-plan(tests => 53 + 27*14);
dcb3b7
+plan(tests => 57 + 27*14);
dcb3b7
 
dcb3b7
 if ($^O =~ /MSWin32|cygwin|msys/ && !is_miniperl) {
dcb3b7
   require Win32; # for IsAdminUser()
dcb3b7
@@ -393,3 +393,11 @@ SKIP: {
dcb3b7
     is $failed_stat2, $failed_stat1,
dcb3b7
 	'failed -r($gv_with_io_but_no_fp) with and w/out fatal warnings';
dcb3b7
 } 
dcb3b7
+
dcb3b7
+{
dcb3b7
+    # [perl #131895] stat() doesn't fail on filenames containing \0 / NUL
dcb3b7
+    ok(!-T "TEST\0-", '-T on name with \0');
dcb3b7
+    ok(!-B "TEST\0-", '-B on name with \0');
dcb3b7
+    ok(!-f "TEST\0-", '-f on name with \0');
dcb3b7
+    ok(!-r "TEST\0-", '-r on name with \0');
dcb3b7
+}
dcb3b7
diff --git a/t/op/stat.t b/t/op/stat.t
dcb3b7
index 637a902..71193ad 100644
dcb3b7
--- a/t/op/stat.t
dcb3b7
+++ b/t/op/stat.t
dcb3b7
@@ -25,7 +25,7 @@ if ($^O eq 'MSWin32') {
dcb3b7
     ${^WIN32_SLOPPY_STAT} = 0;
dcb3b7
 }
dcb3b7
 
dcb3b7
-plan tests => 118;
dcb3b7
+plan tests => 120;
dcb3b7
 
dcb3b7
 my $Perl = which_perl();
dcb3b7
 
dcb3b7
@@ -651,6 +651,16 @@ SKIP:
dcb3b7
       'stat on an array of valid paths should return ENOENT';
dcb3b7
 }
dcb3b7
 
dcb3b7
+# [perl #131895] stat() doesn't fail on filenames containing \0 / NUL
dcb3b7
+ok !stat("TEST\0-"), 'stat on filename with \0';
dcb3b7
+SKIP: {
dcb3b7
+    my $link = "TEST.symlink.$$";
dcb3b7
+    my $can_symlink = eval { symlink "TEST", $link };
dcb3b7
+    skip "cannot symlink", 1 unless $can_symlink;
dcb3b7
+    ok !lstat("$link\0-"), 'lstat on filename with \0';
dcb3b7
+    unlink $link;
dcb3b7
+}
dcb3b7
+
dcb3b7
 END {
dcb3b7
     chmod 0666, $tmpfile;
dcb3b7
     unlink_all $tmpfile;
dcb3b7
-- 
dcb3b7
2.13.6
dcb3b7