Commit f99989f5f30 for php.net

commit f99989f5f300572df99ef4c701770e52eb801a15
Author: Weilin Du <108666168+LamentXU123@users.noreply.github.com>
Date:   Mon May 18 19:35:54 2026 +0800

    ext/phar: improve .phar madic directory preservation logic in phar::addEmptyDir() (#22011)

    Now, the .phar directory is a magic dir for phar files, and in phar::addEmptyDir(), users couldn't create a dir naming .phar

    The implementation is:
    ```c
            if (zend_string_starts_with_literal(dir_name, ".phar")) {
                    zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory");
                    RETURN_THROWS();
    ```
    This has two bugs.

    Firstly, people can use /.phar to create the .phar dir. The leading / will be ignored. (no need to concern about ../ though, it will be ignored.)

    ```php
    <?php
      $phar = new Phar(__DIR__ . '/test.phar', 0, 'test.phar');
      $phar->addEmptyDir('/.phar');
      var_dump(is_dir('phar://' . __DIR__ . '/test.phar/.phar'));
    ```
    Will return true with the .phar dir created, while if the dir is .phar it will raise an error.

    Secondly, it only matches the prefix. That means, /.pharxxx will not be allowed to create, which is not a magic dir.

    ```php
    <?php
      $phar = new Phar(__DIR__ . '/test.phar', 0, 'test.phar');
      $phar->addEmptyDir('.pharx');
    ```
    This will raise an error.
    ```
    PHP Fatal error:  Uncaught BadMethodCallException: Cannot create a directory in magic ".phar" directory in C:\Users\admin\Desktop\bench.php:3
    ```
    This PR fix both by 1. adding a trailing check of the path to make .pharx valid 2. adding a check to /.phar

diff --git a/NEWS b/NEWS
index 65b1f85ba37..f86892d0212 100644
--- a/NEWS
+++ b/NEWS
@@ -129,6 +129,10 @@ PHP                                                                        NEWS
   . Support reference values in Phar::mungServer(). (ndossche)
   . Invalid values now throw in Phar::mungServer() instead of being silently
     ignored. (ndossche)
+  . Fixed a bypass of the magic ".phar" directory protection in
+    Phar::addEmptyDir() for paths starting with "/.phar". (Weilin Du)
+  . Phar::addEmptyDir() now allows non-magic directory names that merely
+    share the ".phar" prefix. (Weilin Du)
   . Support overridden methods in SplFileInfo for getMTime() and getPathname()
     when building a phar. (ndossche)
   . Mark Phar::buildFromIterator() base directory argument as a path.
diff --git a/UPGRADING b/UPGRADING
index 44d6981acbf..5aa617b99fd 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -55,6 +55,11 @@ PHP 8.6 UPGRADE NOTES
 - Phar:
   . Phar::mungServer() now raises a ValueError when an invalid
     argument value is passed instead of being silently ignored.
+  . Phar::addEmptyDir() now rejects `/.phar` paths in addition to `.phar`
+    paths, and raises the same BadMethodCallException for attempts to create
+    the reserved magic ".phar" directory through that form.
+  . Phar::addEmptyDir() now treats non-magic names that merely share the
+    `.phar` prefix as ordinary directories.

 - PGSQL:
   . pg_fetch_object() now reports the ValueError for a non-empty
diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
index 81c4fd14f7b..46db925cfd5 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -3789,9 +3789,16 @@ PHP_METHOD(Phar, addEmptyDir)

 	PHAR_ARCHIVE_OBJECT();

-	if (zend_string_starts_with_literal(dir_name, ".phar")) {
-		zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory");
-		RETURN_THROWS();
+	if (
+		zend_string_starts_with_literal(dir_name, ".phar")
+		|| zend_string_starts_with_literal(dir_name, "/.phar")
+	) {
+		size_t prefix_len = (ZSTR_VAL(dir_name)[0] == '/') + sizeof(".phar") - 1;
+		char next_char = ZSTR_VAL(dir_name)[prefix_len];
+		if (next_char == '/' || next_char == '\\' || next_char == '\0') {
+			zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory");
+			RETURN_THROWS();
+		}
 	}

 	phar_mkdir(&phar_obj->archive, dir_name);
diff --git a/ext/phar/tests/mkdir.phpt b/ext/phar/tests/mkdir.phpt
index 1ffdc7fe252..2c1586b0de5 100644
--- a/ext/phar/tests/mkdir.phpt
+++ b/ext/phar/tests/mkdir.phpt
@@ -24,6 +24,13 @@
 } catch (Exception $e) {
 echo $e->getMessage(),"\n";
 }
+try {
+$a->addEmptyDir('/.phar');
+} catch (Exception $e) {
+echo $e->getMessage(),"\n";
+}
+$a->addEmptyDir('/.pharx');
+var_dump(is_dir($pname . '/.pharx'));
 ?>
 --CLEAN--
 <?php
@@ -43,3 +50,5 @@

 Warning: rmdir(): phar error: cannot remove directory "a" in phar "%smkdir.phar.php", phar error: path "a" exists and is a not a directory in %smkdir.php on line %d
 Cannot create a directory in magic ".phar" directory
+Cannot create a directory in magic ".phar" directory
+bool(true)