[PHP-CVS] [php-src] master: ext/standard: Throw ValueError for filenames with null bytes

[email protected] (Gina Peter Banyard) Thu, 30 Jul 2026 12:22:59 +0000
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Gina Peter Banyard (Girgias)
Date: 2026-07-30T12:59:07+01:00

Commit: https://github.com/php/php-src/commit/d3c95d1745de99b39034f8147f4659182143e6e9
Raw diff: https://github.com/php/php-src/commit/d3c95d1745de99b39034f8147f4659182143e6e9.diff

ext/standard: Throw ValueError for filenames with null bytes

This should never happen in the first place

Changed paths:
  A  ext/standard/tests/file/filegroup_null_byte.phpt
  A  ext/standard/tests/file/fileinode_null_byte.phpt
  A  ext/standard/tests/file/fileowner_null_byte.phpt
  A  ext/standard/tests/file/fileperms_null_byte.phpt
  A  ext/standard/tests/file/is_dir_null_byte.phpt
  A  ext/standard/tests/file/is_executable_null_byte.phpt
  A  ext/standard/tests/file/is_file_null_byte.phpt
  A  ext/standard/tests/file/is_readable_null_byte.phpt
  A  ext/standard/tests/file/is_writable_null_byte.phpt
  M  NEWS
  M  UPGRADING
  M  ext/standard/filestat.c
  M  ext/standard/tests/file/bug39863.phpt
  M  ext/standard/tests/file/filegroup_variation3.phpt
  M  ext/standard/tests/file/fileinode_variation3.phpt
  M  ext/standard/tests/file/fileowner_variation3.phpt
  M  ext/standard/tests/file/fileperms_variation3.phpt
  M  ext/standard/tests/file/is_dir_variation4.phpt
  M  ext/standard/tests/file/is_executable_variation1.phpt
  M  ext/standard/tests/file/is_file_variation4.phpt
  M  ext/standard/tests/file/is_readable_variation1.phpt
  M  ext/standard/tests/file/is_writable_variation1.phpt


Diff:

diff --git a/NEWS b/NEWS
index cb70bda237ff..d90af7f5e64d 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,13 @@ PHP                                                                        NEWS
     error handler instead of emitting a warning and continuing with an empty
     string. (Weilin Du)
 
+- Standard:
+  . The following functions now raise a ValueError when the $filename argument
+    contains NUL bytes: fileperms(), fileinode(), filesize(), fileowner(),
+    filegroup(), fileatime(), filemtime(), filectime(), filetype(),
+    is_writable(), is_readable(), is_executable(), is_file(), is_dir(),
+    is_link(), file_exists(), lstat(), stat(). (Girgias)
+
 30 Jul 2026, PHP 8.6.0alpha3
 
 - Core:
diff --git a/UPGRADING b/UPGRADING
index 599ae2290d6e..b516f24db97f 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -228,6 +228,26 @@ PHP 8.6 UPGRADE NOTES
     bytes.
   . base_convert(), bindex(), hexdec() and octdec() now raise a notice when
     they cannot precisely convert the given number.
+  . The following functions now raise a ValueError when the $filename argument
+    contains NUL bytes:
+    - fileperms()
+    - fileinode()
+    - filesize()
+    - fileowner()
+    - filegroup()
+    - fileatime()
+    - filemtime()
+    - filectime()
+    - filetype()
+    - is_writable()
+    - is_readable()
+    - is_executable()
+    - is_file()
+    - is_dir()
+    - is_link()
+    - file_exists()
+    - lstat()
+    - stat()
 
 - Sysvshm:
   . shm_attach() now raises a ValueError when the $key argument is outside the
diff --git a/ext/standard/filestat.c b/ext/standard/filestat.c
index 7dcfecf1f872..de4fcd82a620 100644
--- a/ext/standard/filestat.c
+++ b/ext/standard/filestat.c
@@ -996,7 +996,7 @@ ZEND_NAMED_FUNCTION(name) { \
 	zend_string *filename; \
 	\
 	ZEND_PARSE_PARAMETERS_START(1, 1) \
-		Z_PARAM_STR(filename) \
+		Z_PARAM_PATH_STR(filename) \
 	ZEND_PARSE_PARAMETERS_END(); \
 	\
 	php_stat(filename, funcnum, return_value); \
diff --git a/ext/standard/tests/file/bug39863.phpt b/ext/standard/tests/file/bug39863.phpt
index 88c569473a76..2a0ca01ea65a 100644
--- a/ext/standard/tests/file/bug39863.phpt
+++ b/ext/standard/tests/file/bug39863.phpt
@@ -6,7 +6,11 @@ Andrew van der Stock, vanderaj @ owasp.org
 <?php
 
 $filename = __FILE__ . chr(0). ".ridiculous";
-var_dump(file_exists($filename));
+try {
+    var_dump(file_exists($filename));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
 ?>
 --EXPECT--
-bool(false)
+ValueError: file_exists(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/filegroup_null_byte.phpt b/ext/standard/tests/file/filegroup_null_byte.phpt
new file mode 100644
index 000000000000..4cb80399c61d
--- /dev/null
+++ b/ext/standard/tests/file/filegroup_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+filegroup() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(filegroup("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: filegroup(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/filegroup_variation3.phpt b/ext/standard/tests/file/filegroup_variation3.phpt
index e844be077212..94562850f58d 100644
--- a/ext/standard/tests/file/filegroup_variation3.phpt
+++ b/ext/standard/tests/file/filegroup_variation3.phpt
@@ -26,10 +26,6 @@ $files_arr = array(
   "//filegroup_variation3//filegroup_variation3.tmp",
   "/filegroup_variation3/*.tmp",
   "filegroup_variation3/filegroup*.tmp",
-
-  /* Testing Binary safe */
-  "/filegroup_variation3/filegroup_variation3.tmp".chr(0),
-  "/filegroup_variation3/filegroup_variation3.tmp\0"
 );
 
 $count = 1;
@@ -74,13 +70,5 @@ bool(false)
 
 Warning: filegroup(): stat failed for %s/filegroup_variation3/filegroup*.tmp in %s on line %d
 bool(false)
-- Iteration 7 -
-
-Warning: filegroup(): Filename contains null byte in %s on line %d
-bool(false)
-- Iteration 8 -
-
-Warning: filegroup(): Filename contains null byte in %s on line %d
-bool(false)
 
 *** Done ***
diff --git a/ext/standard/tests/file/fileinode_null_byte.phpt b/ext/standard/tests/file/fileinode_null_byte.phpt
new file mode 100644
index 000000000000..48b8272d97cf
--- /dev/null
+++ b/ext/standard/tests/file/fileinode_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+fileinode() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(fileinode("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: fileinode(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/fileinode_variation3.phpt b/ext/standard/tests/file/fileinode_variation3.phpt
index 92357a3619e8..506984d41775 100644
--- a/ext/standard/tests/file/fileinode_variation3.phpt
+++ b/ext/standard/tests/file/fileinode_variation3.phpt
@@ -25,10 +25,6 @@ $files_arr = array(
   "//fileinode_variation3//fileinode_variation3.tmp",
   "/fileinode_variation3/*.tmp",
   "fileinode_variation3/fileinode*.tmp",
-
-  /* Testing Binary safe */
-  "/fileinode_variation3/fileinode_variation3.tmp".chr(0),
-  "/fileinode_variation3/fileinode_variation3.tmp\0"
 );
 
 $count = 1;
@@ -73,13 +69,5 @@ bool(false)
 
 Warning: fileinode(): stat failed for %s/fileinode_variation3/fileinode*.tmp in %s on line %d
 bool(false)
-- Iteration 7 -
-
-Warning: fileinode(): Filename contains null byte in %s on line %d
-bool(false)
-- Iteration 8 -
-
-Warning: fileinode(): Filename contains null byte in %s on line %d
-bool(false)
 
 *** Done ***
diff --git a/ext/standard/tests/file/fileowner_null_byte.phpt b/ext/standard/tests/file/fileowner_null_byte.phpt
new file mode 100644
index 000000000000..909ac0fe3109
--- /dev/null
+++ b/ext/standard/tests/file/fileowner_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+fileowner() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(fileowner("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: fileowner(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/fileowner_variation3.phpt b/ext/standard/tests/file/fileowner_variation3.phpt
index 63ba6936aaa8..e6360b475713 100644
--- a/ext/standard/tests/file/fileowner_variation3.phpt
+++ b/ext/standard/tests/file/fileowner_variation3.phpt
@@ -26,10 +26,6 @@ $files_arr = array(
   "//fileowner_variation3//fileowner_variation3.tmp",
   "/fileowner_variation3/*.tmp",
   "fileowner_variation3/fileowner*.tmp",
-
-  /* Testing Binary safe */
-  "/fileowner_variation3/fileowner_variation3.tmp".chr(0),
-  "/fileowner_variation3/fileowner_variation3.tmp\0"
 );
 
 $count = 1;
@@ -74,13 +70,5 @@ bool(false)
 
 Warning: fileowner(): stat failed for %s/fileowner_variation3/fileowner*.tmp in %s on line %d
 bool(false)
-- Iteration 7 -
-
-Warning: fileowner(): Filename contains null byte in %s on line %d
-bool(false)
-- Iteration 8 -
-
-Warning: fileowner(): Filename contains null byte in %s on line %d
-bool(false)
 
 *** Done ***
diff --git a/ext/standard/tests/file/fileperms_null_byte.phpt b/ext/standard/tests/file/fileperms_null_byte.phpt
new file mode 100644
index 000000000000..1530d0e5bf9f
--- /dev/null
+++ b/ext/standard/tests/file/fileperms_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+fileperms() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(fileperms("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: fileperms(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/fileperms_variation3.phpt b/ext/standard/tests/file/fileperms_variation3.phpt
index 5e981e9b8632..6c1da17d6bfd 100644
--- a/ext/standard/tests/file/fileperms_variation3.phpt
+++ b/ext/standard/tests/file/fileperms_variation3.phpt
@@ -25,10 +25,6 @@ $files_arr = array(
   "//fileperms_variation3//fileperms_variation3.tmp",
   "/fileperms_variation3/*.tmp",
   "fileperms_variation3/fileperms*.tmp",
-
-  /* Testing Binary safe */
-  "/fileperms_variation3/fileperms_variation3.tmp".chr(0),
-  "/fileperms_variation3/fileperms_variation3.tmp\0"
 );
 
 $count = 1;
@@ -73,13 +69,5 @@ bool(false)
 
 Warning: fileperms(): stat failed for %s/fileperms_variation3/fileperms*.tmp in %s on line %d
 bool(false)
-- Iteration 7 -
-
-Warning: fileperms(): Filename contains null byte in %s on line %d
-bool(false)
-- Iteration 8 -
-
-Warning: fileperms(): Filename contains null byte in %s on line %d
-bool(false)
 
 *** Done ***
diff --git a/ext/standard/tests/file/is_dir_null_byte.phpt b/ext/standard/tests/file/is_dir_null_byte.phpt
new file mode 100644
index 000000000000..a49ccc0e5c45
--- /dev/null
+++ b/ext/standard/tests/file/is_dir_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+is_dir() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(is_dir("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: is_dir(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/is_dir_variation4.phpt b/ext/standard/tests/file/is_dir_variation4.phpt
index d6e64efe211c..30285804a33c 100644
--- a/ext/standard/tests/file/is_dir_variation4.phpt
+++ b/ext/standard/tests/file/is_dir_variation4.phpt
@@ -23,10 +23,6 @@ $dirs_arr = array(
   "./is_dir_variation4//",
   ".//is_dir_variation4//",
   "is_dir_vari*",
-
-  /* Testing Binary safe */
-  "./is_dir_variation4/".chr(0),
-  "is_dir_variation4\0"
 );
 
 $count = 1;
@@ -75,10 +71,4 @@ bool(true)
 -- Iteration 8 --
 bool(false)
 
--- Iteration 9 --
-bool(false)
-
--- Iteration 10 --
-bool(false)
-
 *** Done ***
diff --git a/ext/standard/tests/file/is_executable_null_byte.phpt b/ext/standard/tests/file/is_executable_null_byte.phpt
new file mode 100644
index 000000000000..7c26d53061d1
--- /dev/null
+++ b/ext/standard/tests/file/is_executable_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+is_executable() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(is_executable("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: is_executable(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/is_executable_variation1.phpt b/ext/standard/tests/file/is_executable_variation1.phpt
index a85b9a2ef028..289abb1a265f 100644
--- a/ext/standard/tests/file/is_executable_variation1.phpt
+++ b/ext/standard/tests/file/is_executable_variation1.phpt
@@ -33,11 +33,6 @@ $files_arr = array(
   "$file_path/is_executable_variation1/*.tmp",
   "$file_path/is_executable_variation1/b*.tmp",
 
-  /* Testing Binary safe */
-  "$file_path/is_executable_variation1".chr(0)."bar.temp",
-  "$file_path".chr(0)."is_executable_variation1/bar.temp",
-  "$file_path/is_executable_variation1x000/",
-
   /* Testing directories */
   ".",  // current directory, exp: bool(true)
   "$file_path/is_executable_variation1"  // temp directory, exp: bool(true)
@@ -76,13 +71,7 @@ bool(false)
 -- Iteration 5 --
 bool(false)
 -- Iteration 6 --
-bool(false)
--- Iteration 7 --
-bool(false)
--- Iteration 8 --
-bool(false)
--- Iteration 9 --
 bool(true)
--- Iteration 10 --
+-- Iteration 7 --
 bool(true)
 Done
diff --git a/ext/standard/tests/file/is_file_null_byte.phpt b/ext/standard/tests/file/is_file_null_byte.phpt
new file mode 100644
index 000000000000..f544d7bcf589
--- /dev/null
+++ b/ext/standard/tests/file/is_file_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+is_file() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(is_file("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: is_file(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/is_file_variation4.phpt b/ext/standard/tests/file/is_file_variation4.phpt
index 1afc34dd03be..9d98b7fd90d7 100644
--- a/ext/standard/tests/file/is_file_variation4.phpt
+++ b/ext/standard/tests/file/is_file_variation4.phpt
@@ -23,10 +23,6 @@ $files_arr = array(
   "//is_file_variation4//is_file_variation4.tmp",
   "/is_file_variation4/*.tmp",
   "is_file_variation4/is_file*.tmp",
-
-  /* Testing Binary safe */
-  "/is_file_variation4/is_file_variation4.tmp".chr(0),
-  "/is_file_variation4/is_file_variation4.tmp\0"
 );
 
 $count = 1;
@@ -65,9 +61,5 @@ bool(true)
 bool(false)
 - Iteration 6 -
 bool(false)
-- Iteration 7 -
-bool(false)
-- Iteration 8 -
-bool(false)
 
 *** Done ***
diff --git a/ext/standard/tests/file/is_readable_null_byte.phpt b/ext/standard/tests/file/is_readable_null_byte.phpt
new file mode 100644
index 000000000000..711700f4e7ec
--- /dev/null
+++ b/ext/standard/tests/file/is_readable_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+is_readable() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(is_readable("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: is_readable(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/is_readable_variation1.phpt b/ext/standard/tests/file/is_readable_variation1.phpt
index 9c25213f8abb..8548a840ccbe 100644
--- a/ext/standard/tests/file/is_readable_variation1.phpt
+++ b/ext/standard/tests/file/is_readable_variation1.phpt
@@ -32,11 +32,6 @@ $files_arr = array(
   "$file_path/is_readable_variation1/*.tmp",
   "$file_path/is_readable_variation1/b*.tmp",
 
-  /* Testing Binary safe */
-  "$file_path/is_readable_variation1".chr(0)."bar.tmp",
-  "$file_path".chr(0)."is_readable_variation1/bar.tmp",
-  "$file_path".chr(0)."is_readable_variation1/bar.tmp",
-
   /* Testing directories */
   ".",  // current directory, exp: bool(true)
   "$file_path/is_readable_variation1"  // temp directory, exp: bool(true)
@@ -77,13 +72,7 @@ bool(false)
 -- Iteration 6 --
 bool(false)
 -- Iteration 7 --
-bool(false)
--- Iteration 8 --
-bool(false)
--- Iteration 9 --
-bool(false)
--- Iteration 10 --
 bool(true)
--- Iteration 11 --
+-- Iteration 8 --
 bool(true)
 Done
diff --git a/ext/standard/tests/file/is_writable_null_byte.phpt b/ext/standard/tests/file/is_writable_null_byte.phpt
new file mode 100644
index 000000000000..319db04cc151
--- /dev/null
+++ b/ext/standard/tests/file/is_writable_null_byte.phpt
@@ -0,0 +1,14 @@
+--TEST--
+is_writable() with filenames with null bytes
+--FILE--
+<?php
+
+try {
+    var_dump(is_writable("file_with_null_byte.tmp\0"));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ValueError: is_writable(): Argument #1 ($filename) must not contain any null bytes
diff --git a/ext/standard/tests/file/is_writable_variation1.phpt b/ext/standard/tests/file/is_writable_variation1.phpt
index 80695d6d45a2..181b1ba4e268 100644
--- a/ext/standard/tests/file/is_writable_variation1.phpt
+++ b/ext/standard/tests/file/is_writable_variation1.phpt
@@ -31,11 +31,6 @@ $files_arr = array(
   "$file_path/is_writable_variation1/*.tmp",
   "$file_path/is_writable_variation1/b*.tmp",
 
-  /* Testing Binary safe */
-  "$file_path/is_writable_variation1".chr(0)."bar.tmp",
-  "$file_path".chr(0)."is_writable_variation1/bar.tmp",
-  "$file_path".chr(0)."is_writable_variation1/bar.tmp",
-
   /* Testing directories */
   ".",  // current directory, exp: bool(true)
   "$file_path/is_writable_variation1"  // temp directory, exp: bool(true)
@@ -87,18 +82,9 @@ bool(false)
 bool(false)
 bool(false)
 -- Iteration 7 --
-bool(false)
-bool(false)
--- Iteration 8 --
-bool(false)
-bool(false)
--- Iteration 9 --
-bool(false)
-bool(false)
--- Iteration 10 --
 bool(true)
 bool(true)
--- Iteration 11 --
+-- Iteration 8 --
 bool(true)
 bool(true)
 Done