[packages/php/PHP_5_5] Rel 49. BR mysql-devel < 8.0: mysqli needs my_global.h (rel 48 failed against mysql9.7-devel). Port

arekm arekm at pld-linux.org
Thu Oct 22 10:57:17 CEST 2026


commit 01f2cd56517ea1eca6960f5aff868297383a748f
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Thu Oct 22 10:56:57 2026 +0200

    Rel 49. BR mysql-devel < 8.0: mysqli needs my_global.h (rel 48 failed against mysql9.7-devel).
    Port the session upload progress part of GHSA-6wmf-4rv5-p3rp instead of disabling it; add the CVE-2020-7070 test updates.

 php-bug70213.patch            |   4 +-
 php-cve-2020-7070.patch       |  74 +++++
 php-cve-2026-109618.patch     | 718 +++++++++++++++++++++++++++++++++++++++++-
 php-ghsa-www2-q4fc-65wf.patch |  10 +-
 php.spec                      |   5 +-
 5 files changed, 794 insertions(+), 17 deletions(-)
---
diff --git a/php.spec b/php.spec
index af95902..d8fada3 100644
--- a/php.spec
+++ b/php.spec
@@ -140,7 +140,7 @@ ERROR: You need to select at least one Apache SAPI to build shared modules.
 %undefine	with_filter
 %endif
 
-%define		rel	48
+%define		rel	49
 %define		orgname	php
 %define		ver_suffix 55
 %define		php_suffix %{!?with_default_php:%{ver_suffix}}
@@ -478,7 +478,8 @@ BuildRequires:	elfutils-devel
 %{?with_enchant:BuildRequires:	enchant-devel >= 1.1.3}
 %{?with_kerberos5:BuildRequires:	heimdal-devel}
 %{?with_system_libzip:BuildRequires:	libzip-devel >= 0.10.1-2}
-%{!?with_mysqlnd:BuildRequires:	mysql-devel}
+# ext/mysqli includes my_global.h, which MySQL 8.0 dropped from its client headers
+%{!?with_mysqlnd:BuildRequires:	mysql-devel < 8.0}
 BuildRequires:	pkgconfig
 BuildRequires:	sed >= 4.0
 %if %{with mssql} || %{with sybase_ct}
diff --git a/php-bug70213.patch b/php-bug70213.patch
index dd064c6..2b17f34 100644
--- a/php-bug70213.patch
+++ b/php-bug70213.patch
@@ -43,7 +43,7 @@ new file mode 100644
 diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re
 --- a/ext/standard/var_unserializer.re
 +++ b/ext/standard/var_unserializer.re
-@@ -841,6 +841,7 @@ object ":" uiv ":" ["]	{
+@@ -874,6 +874,7 @@ object ":" uiv ":" ["]	{
  		}
  
  		/* The callback function may have defined the class */
@@ -51,7 +51,7 @@ diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re
  		if (zend_lookup_class(class_name, len2, &pce TSRMLS_CC) == SUCCESS) {
  			ce = *pce;
  		} else {
-@@ -848,6 +849,7 @@ object ":" uiv ":" ["]	{
+@@ -881,6 +882,7 @@ object ":" uiv ":" ["]	{
  			incomplete_class = 1;
  			ce = PHP_IC_ENTRY;
  		}
diff --git a/php-cve-2020-7070.patch b/php-cve-2020-7070.patch
index f8543e1..82bb3b4 100644
--- a/php-cve-2020-7070.patch
+++ b/php-cve-2020-7070.patch
@@ -3,6 +3,7 @@ Date: Sun Sep 20 18:08:55 2020 -0700
 Subject: [PATCH] Do not decode cookie names anymore
 
 5.5 backport: https://github.com/shivammathur/php-src-backports/commit/fae0bd61b0539ea05bd949bd70bfbc97d02b93ce
+PLD: with the test updates of upstream 6559fe912661 (tests/basic/022, 023, bug79699), which the branch left out
 ---
 diff --git a/main/php_variables.c b/main/php_variables.c
 --- a/main/php_variables.c
@@ -29,3 +30,76 @@ diff --git a/main/php_variables.c b/main/php_variables.c
  			val_len = 0;
  			val = estrndup("", val_len);
  			if (sapi_module.input_filter(arg, var, &val, val_len, &new_val_len TSRMLS_CC)) {
+diff --git a/tests/basic/022.phpt b/tests/basic/022.phpt
+--- a/tests/basic/022.phpt
++++ b/tests/basic/022.phpt
+@@ -10,7 +10,7 @@ cookie1=val1  ; cookie2=val2%20; cookie3=val 3.; cookie 4= value 4 %3B; cookie1=
+ var_dump($_COOKIE);
+ ?>
+ --EXPECT--
+-array(10) {
++array(12) {
+   ["cookie1"]=>
+   string(6) "val1  "
+   ["cookie2"]=>
+@@ -19,11 +19,15 @@ array(10) {
+   string(6) "val 3."
+   ["cookie_4"]=>
+   string(10) " value 4 ;"
++  ["%20cookie1"]=>
++  string(6) "ignore"
++  ["+cookie1"]=>
++  string(6) "ignore"
+   ["cookie__5"]=>
+   string(7) "  value"
+-  ["cookie_6"]=>
++  ["cookie%206"]=>
+   string(3) "���"
+-  ["cookie_7"]=>
++  ["cookie+7"]=>
+   string(0) ""
+   ["$cookie_8"]=>
+   string(0) ""
+diff --git a/tests/basic/023.phpt b/tests/basic/023.phpt
+--- a/tests/basic/023.phpt
++++ b/tests/basic/023.phpt
+@@ -10,9 +10,11 @@ c o o k i e=value; c o o k i e= v a l u e ;;c%20o+o k+i%20e=v;name="value","valu
+ var_dump($_COOKIE);
+ ?>
+ --EXPECT--
+-array(3) {
++array(4) {
+   ["c_o_o_k_i_e"]=>
+   string(5) "value"
++  ["c%20o+o_k+i%20e"]=>
++  string(1) "v"
+   ["name"]=>
+   string(24) ""value","value",UEhQIQ=="
+   ["UEhQIQ"]=>
+diff --git a/tests/basic/bug79699.phpt b/tests/basic/bug79699.phpt
+new file mode 100644
+--- /dev/null
++++ b/tests/basic/bug79699.phpt
+@@ -0,0 +1,22 @@
++--TEST--
++Cookies Security Bug
++--INI--
++max_input_vars=1000
++filter.default=unsafe_raw
++--COOKIE--
++__%48ost-evil=evil; __Host-evil=good; %66oo=baz;foo=bar
++--FILE--
++<?php
++var_dump($_COOKIE);
++?>
++--EXPECT--
++array(4) {
++  ["__%48ost-evil"]=>
++  string(4) "evil"
++  ["__Host-evil"]=>
++  string(4) "good"
++  ["%66oo"]=>
++  string(3) "baz"
++  ["foo"]=>
++  string(3) "bar"
++}
diff --git a/php-cve-2026-109618.patch b/php-cve-2026-109618.patch
index 612e35e..a623ab0 100644
--- a/php-cve-2026-109618.patch
+++ b/php-cve-2026-109618.patch
@@ -5,6 +5,8 @@ Subject: [PATCH] Avoid state corruption via bailout on PHP 5.5
 Upstream: https://github.com/php/php-src/commit/fa146c84c1043662c7252496a50b9ff8e7500092
 
 5.5 backport: https://github.com/shivammathur/php-src-backports/commit/ce192aa883e9fe2e41090c791bc50d80f147ebf2
+PLD: session upload progress fix ported from upstream 3584a98bda0f (GHSA-6wmf-4rv5-p3rp) instead of the branch's "|| 1", which switched the feature off; with its tests
+PLD: incomplete_classes_only is a BG() flag set by php_var_set_incomplete_classes_only() rather than a var_hash member: PHP_VAR_*_INIT() expand in extensions, so the var_hash layout is ABI
 ---
 diff --git a/Zend/zend_API.c b/Zend/zend_API.c
 --- a/Zend/zend_API.c
@@ -402,15 +404,707 @@ diff --git a/Zend/zend_strtod.c b/Zend/zend_strtod.c
 diff --git a/ext/session/session.c b/ext/session/session.c
 --- a/ext/session/session.c
 +++ b/ext/session/session.c
-@@ -2630,7 +2630,7 @@ static int php_session_rfc1867_callback(unsigned int event, void *event_data, vo
- 	if (php_session_rfc1867_orig_callback) {
- 		retval = php_session_rfc1867_orig_callback(event, event_data, extra TSRMLS_CC);
+@@ -2603,23 +2603,34 @@ static void php_session_rfc1867_update(php_session_rfc1867_progress *progress, i
+ 		progress->next_update = Z_LVAL_P(progress->post_bytes_processed) + progress->update_step;
  	}
--	if (!PS(rfc1867_enabled)) {
-+	if (!PS(rfc1867_enabled) || 1) {
+ 
++	/* Runs before RINIT: no user code may be invoked, see php_var_set_incomplete_classes_only() */
++	php_var_set_incomplete_classes_only(1 TSRMLS_CC);
+ 	php_session_initialize(TSRMLS_C);
+-	PS(session_status) = php_session_active;
++	if (PS(session_status) != php_session_active) {
++		php_var_set_incomplete_classes_only(0 TSRMLS_CC);
++		return;
++	}
+ 	IF_SESSION_VARS() {
+ 		progress->cancel_upload |= php_check_cancel_upload(progress TSRMLS_CC);
+ 		ZEND_SET_SYMBOL_WITH_LENGTH(Z_ARRVAL_P(PS(http_session_vars)), progress->key.c, progress->key.len+1, progress->data, 2, 0);
+ 	}
+ 	php_session_flush(TSRMLS_C);
++	php_var_set_incomplete_classes_only(0 TSRMLS_CC);
+ } /* }}} */
+ 
+ static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress TSRMLS_DC) /* {{{ */
+ {
++	php_var_set_incomplete_classes_only(1 TSRMLS_CC);
+ 	php_session_initialize(TSRMLS_C);
+-	PS(session_status) = php_session_active;
++	if (PS(session_status) != php_session_active) {
++		php_var_set_incomplete_classes_only(0 TSRMLS_CC);
++		return;
++	}
+ 	IF_SESSION_VARS() {
+ 		zend_hash_del(Z_ARRVAL_P(PS(http_session_vars)), progress->key.c, progress->key.len+1);
+ 	}
+ 	php_session_flush(TSRMLS_C);
++	php_var_set_incomplete_classes_only(0 TSRMLS_CC);
+ } /* }}} */
+ 
+ static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra TSRMLS_DC) /* {{{ */
+@@ -2633,12 +2644,20 @@ static int php_session_rfc1867_callback(unsigned int event, void *event_data, vo
+ 	if (!PS(rfc1867_enabled)) {
  		return retval;
  	}
++	if (PG(modules_activated)) {
++		/* APACHE_HOOKS parses the body after RINIT. Do not run here as it corrupts the session. */
++		return retval;
++	}
+ 
+ 	progress = PS(rfc1867_progress);
+ 
+ 	switch(event) {
+ 		case MULTIPART_EVENT_START: {
+ 			multipart_event_start *data = (multipart_event_start *) event_data;
++
++			/* This runs before RINIT, so we need to clear these early */
++			php_rinit_session_globals(TSRMLS_C);
++
+ 			progress = ecalloc(1, sizeof(php_session_rfc1867_progress));
+ 			progress->content_length = data->content_length;
+ 			progress->sname_len  = strlen(PS(session_name));
+@@ -2798,6 +2817,9 @@ static int php_session_rfc1867_callback(unsigned int event, void *event_data, vo
+ 			efree(progress);
+ 			progress = NULL;
+ 			PS(rfc1867_progress) = NULL;
++
++			/* $_SESSION was populated in incomplete_classes_only mode */
++			zend_delete_global_variable("_SESSION", sizeof("_SESSION") - 1 TSRMLS_CC);
+ 		}
+ 		break;
+ 	}
+diff --git a/ext/session/tests/rfc1867_incomplete_class_reserved_props.phpt b/ext/session/tests/rfc1867_incomplete_class_reserved_props.phpt
+new file mode 100644
+--- /dev/null
++++ b/ext/session/tests/rfc1867_incomplete_class_reserved_props.phpt
+@@ -0,0 +1,147 @@
++--TEST--
++session rfc1867: the upload progress update rejects session data whose objects carry the incomplete class bookkeeping property names
++--SKIPIF--
++<?php
++include('skipif.inc');
++if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
++    die('skip CGI not available');
++}
++?>
++--FILE--
++<?php
++
++$dir = __DIR__ . '/rfc1867_incomplete_class_reserved_props';
++ at mkdir($dir);
++$sid = 'rfc1867reservedprops';
++$file = "$dir/sess_$sid";
++$worker = "$dir/worker.php";
++file_put_contents($worker, '<?php');
++
++/* The handler decodes the record with every object kept as
++ * __PHP_Incomplete_Class, which stores the class name of each object and the
++ * payload of C: entries under these two property names. Data that already
++ * carries one of them cannot be told apart from that bookkeeping, so the decode
++ * has to fail rather than let the two meanings be confused. */
++$cases = array(
++    'control'          => 'x|O:8:"stdClass":1:{s:1:"a";i:1;}',
++    'Class_Name'       => 'x|O:8:"stdClass":1:{s:27:"__PHP_Incomplete_Class_Name";s:3:"foo";}',
++    'Class_Serialized' => 'x|O:8:"stdClass":1:{s:33:"__PHP_Incomplete_Class_Serialized";s:3:"foo";}',
++    'nested'           => 'x|a:1:{i:0;O:8:"stdClass":1:{s:27:"__PHP_Incomplete_Class_Name";s:3:"foo";}}',
++    'mangled'          => 'x|O:8:"stdClass":1:{s:30:"' . "\0" . 'A' . "\0" . '__PHP_Incomplete_Class_Name";s:3:"foo";}',
++    'corrupt'          => 'x|not-serialized-at-all',
++);
++
++$boundary = '---------------------------20896060251896012921717172737';
++$body = "--$boundary\r\n"
++      . "Content-Disposition: form-data; name=\"PHPSESSID\"\r\n\r\n$sid\r\n"
++      . "--$boundary\r\n"
++      . "Content-Disposition: form-data; name=\"PHP_SESSION_UPLOAD_PROGRESS\"\r\n\r\nkey\r\n"
++      . "--$boundary\r\n"
++      . "Content-Disposition: form-data; name=\"file1\"; filename=\"file1.txt\"\r\n\r\n1\r\n"
++      . "--$boundary--\r\n";
++
++$ini = array(
++    'error_reporting' => 'E_ALL',
++    /* the update runs during request startup; On by default since PHP 8.0 */
++    'display_startup_errors' => '1',
++    'html_errors' => '0',
++    'file_uploads' => '1',
++    'upload_max_filesize' => '1024',
++    'session.save_path' => $dir,
++    'session.name' => 'PHPSESSID',
++    'session.use_strict_mode' => '0',
++    'session.use_cookies' => '0',
++    'session.use_only_cookies' => '0',
++    'session.cache_limiter' => '',
++    'session.save_handler' => 'files',
++    'session.serialize_handler' => 'php',
++    'session.upload_progress.enabled' => '1',
++    'session.upload_progress.cleanup' => '0',
++    'session.upload_progress.prefix' => 'upload_progress_',
++    'session.upload_progress.name' => 'PHP_SESSION_UPLOAD_PROGRESS',
++    'session.upload_progress.freq' => '1%',
++    'session.upload_progress.min_freq' => '0.000000001',
++);
++
++/* php-cgi is started with -n, so shared modules have to be named again;
++ * session requires spl. */
++$extension_dir = ini_get('extension_dir');
++$modules = array();
++foreach (array('spl', 'session') as $ext) {
++    foreach (array("$ext.", "php_$ext.") as $prefix) {
++        if ($extension_dir !== false && file_exists("$extension_dir/$prefix" . PHP_SHLIB_SUFFIX)) {
++            $ini['extension_dir'] = $extension_dir;
++            $modules[] = $prefix . PHP_SHLIB_SUFFIX;
++            break;
++        }
++    }
++}
++
++$cmd = escapeshellarg(getenv('TEST_PHP_CGI_EXECUTABLE')) . ' -n -q';
++foreach ($ini as $name => $value) {
++    $cmd .= ' -d ' . escapeshellarg("$name=$value");
++}
++foreach ($modules as $module) {
++    $cmd .= ' -d ' . escapeshellarg("extension=$module");
++}
++$cmd .= ' -f ' . escapeshellarg($worker);
++
++$env = array();
++foreach (array('PATH', 'LD_LIBRARY_PATH', 'TMPDIR') as $name) {
++    if (getenv($name) !== false) {
++        $env[$name] = getenv($name);
++    }
++}
++$env['REDIRECT_STATUS'] = '1';
++$env['REQUEST_METHOD'] = 'POST';
++$env['CONTENT_TYPE'] = "multipart/form-data; boundary=$boundary";
++$env['CONTENT_LENGTH'] = (string) strlen($body);
++$env['SCRIPT_FILENAME'] = $worker;
++$env['PATH_TRANSLATED'] = $worker;
++$env['QUERY_STRING'] = '';
++
++foreach ($cases as $label => $record) {
++    file_put_contents($file, $record);
++
++    $pipes = array();
++    $proc = proc_open(
++        $cmd,
++        array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')),
++        $pipes,
++        $dir,
++        $env
++    );
++    fwrite($pipes[0], $body);
++    fclose($pipes[0]);
++    $out = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
++    fclose($pipes[1]);
++    fclose($pipes[2]);
++    proc_close($proc);
++
++    /* A refused decode must leave exactly one warning: the handler stops there
++     * instead of writing the progress into the empty session it was left with,
++     * which would add a second warning about the save path. */
++    $kept = file_exists($file) && strncmp(file_get_contents($file), $record, strlen($record)) === 0;
++    printf("%-16s warnings: %d, decode refused: %-5s record kept: %s\n", $label,
++        substr_count($out, 'Warning:'),
++        var_export(strpos($out, 'Session has been destroyed') !== false, true),
++        var_export($kept, true));
++}
++
++?>
++--CLEAN--
++<?php
++$dir = __DIR__ . '/rfc1867_incomplete_class_reserved_props';
++ at unlink("$dir/worker.php");
++foreach (glob("$dir/sess_*") as $f) {
++    unlink($f);
++}
++ at rmdir($dir);
++?>
++--EXPECT--
++control          warnings: 0, decode refused: false record kept: true
++Class_Name       warnings: 1, decode refused: true  record kept: false
++Class_Serialized warnings: 1, decode refused: true  record kept: false
++nested           warnings: 1, decode refused: true  record kept: false
++mangled          warnings: 0, decode refused: false record kept: true
++corrupt          warnings: 1, decode refused: true  record kept: false
+diff --git a/ext/session/tests/rfc1867_no_user_code.phpt b/ext/session/tests/rfc1867_no_user_code.phpt
+new file mode 100644
+--- /dev/null
++++ b/ext/session/tests/rfc1867_no_user_code.phpt
+@@ -0,0 +1,203 @@
++--TEST--
++session rfc1867: the upload progress update resolves no classes, keeps the session data intact and leaves no $_SESSION behind
++--SKIPIF--
++<?php
++include('skipif.inc');
++if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
++    die('skip CGI not available');
++}
++?>
++--FILE--
++<?php
++
++$dir = __DIR__ . '/rfc1867_no_user_code';
++ at mkdir($dir);
++$sid = 'rfc1867nousercode';
++$file = "$dir/sess_$sid";
++$worker = "$dir/worker.php";
++
++/* A hand-written record, as session_encode() produces it for the `php` handler:
++ * an object with __wakeup(), a Serializable payload (C:) and a class that does
++ * not exist. Writing it directly leaves the session module untouched until the
++ * upload progress handler runs, as in a real request. */
++$record = 'w|O:1:"W":1:{s:1:"a";i:1;}'
++        . 'c|C:4:"Cust":7:{payload}'
++        . 'm|O:7:"Missing":1:{s:1:"x";i:3;}';
++file_put_contents($file, $record);
++
++/* None of these classes exist while the handler decodes the record: the request
++ * script is not even compiled yet. Resolving them, C:4:"Cust" would lose its
++ * payload. The handler keeps every object as __PHP_Incomplete_Class instead and
++ * writes it back unchanged. */
++file_put_contents($worker, <<<'PHP'
++<?php
++$record = getenv('TEST_SESSION_RECORD');
++$file = getenv('TEST_SESSION_FILE');
++
++spl_autoload_register(function ($class) { echo "autoload($class)\n"; });
++
++class W {
++    public $a = 1;
++    function __wakeup() {
++        echo "W::__wakeup\n";
++    }
++    function __sleep() {
++        echo "W::__sleep\n"; return array('a');
++    }
++}
++
++class Cust implements Serializable {
++    public $x = 'payload';
++    function serialize() {
++        return $this->x;
++    }
++    function unserialize($d) {
++        $this->x = $d;
++    }
++}
++
++/* The handler needs a $_SESSION to decode the record into, and registers it in
++ * the global symbol table. This script has not called session_start(), so it
++ * must not be left behind: it would otherwise expose the session data of
++ * whatever id the request body carried. */
++var_dump(array_key_exists('_SESSION', $GLOBALS));
++var_dump(session_status() === PHP_SESSION_NONE);
++
++echo "--- record after the update:\n";
++$after = file_get_contents($file);
++var_dump(substr($after, 0, strlen($record)) === $record);
++var_dump(substr($after, strlen($record), strlen('upload_progress_key|')));
++
++echo "--- session_start():\n";
++session_start();
++var_dump(session_id());
++var_dump($_SESSION['w']->a, $_SESSION['c']->x);
++var_dump($_SESSION['m']);
++var_dump(isset($_SESSION['upload_progress_key']['bytes_processed']));
++session_destroy();
++PHP
++);
++
++$boundary = '---------------------------20896060251896012921717172737';
++$body = "--$boundary\r\n"
++      . "Content-Disposition: form-data; name=\"PHPSESSID\"\r\n\r\n$sid\r\n"
++      . "--$boundary\r\n"
++      . "Content-Disposition: form-data; name=\"PHP_SESSION_UPLOAD_PROGRESS\"\r\n\r\nkey\r\n"
++      . "--$boundary\r\n"
++      . "Content-Disposition: form-data; name=\"file1\"; filename=\"file1.txt\"\r\n\r\n1\r\n"
++      . "--$boundary--\r\n";
++
++$ini = array(
++    'error_reporting' => 'E_ALL&~E_DEPRECATED',
++    /* the update runs during request startup; On by default since PHP 8.0 */
++    'display_startup_errors' => '1',
++    'file_uploads' => '1',
++    'upload_max_filesize' => '1024',
++    'session.save_path' => $dir,
++    'session.name' => 'PHPSESSID',
++    'session.use_strict_mode' => '0',
++    'session.use_cookies' => '0',
++    'session.use_only_cookies' => '0',
++    'session.cache_limiter' => '',
++    'session.save_handler' => 'files',
++    'session.serialize_handler' => 'php',
++    'session.upload_progress.enabled' => '1',
++    'session.upload_progress.cleanup' => '0',
++    'session.upload_progress.prefix' => 'upload_progress_',
++    'session.upload_progress.name' => 'PHP_SESSION_UPLOAD_PROGRESS',
++    'session.upload_progress.freq' => '1%',
++    'session.upload_progress.min_freq' => '0.000000001',
++);
++
++/* php-cgi is started with -n, so shared modules have to be named again;
++ * session requires spl. */
++$extension_dir = ini_get('extension_dir');
++$modules = array();
++foreach (array('spl', 'session') as $ext) {
++    foreach (array("$ext.", "php_$ext.") as $prefix) {
++        if ($extension_dir !== false && file_exists("$extension_dir/$prefix" . PHP_SHLIB_SUFFIX)) {
++            $ini['extension_dir'] = $extension_dir;
++            $modules[] = $prefix . PHP_SHLIB_SUFFIX;
++            break;
++        }
++    }
++}
++
++$cmd = escapeshellarg(getenv('TEST_PHP_CGI_EXECUTABLE')) . ' -n -q';
++foreach ($ini as $name => $value) {
++    $cmd .= ' -d ' . escapeshellarg("$name=$value");
++}
++foreach ($modules as $module) {
++    $cmd .= ' -d ' . escapeshellarg("extension=$module");
++}
++$cmd .= ' -f ' . escapeshellarg($worker);
++
++$env = array();
++foreach (array('PATH', 'LD_LIBRARY_PATH', 'TMPDIR') as $name) {
++    if (getenv($name) !== false) {
++        $env[$name] = getenv($name);
++    }
++}
++$env['REDIRECT_STATUS'] = '1';
++$env['REQUEST_METHOD'] = 'POST';
++$env['CONTENT_TYPE'] = "multipart/form-data; boundary=$boundary";
++$env['CONTENT_LENGTH'] = (string) strlen($body);
++$env['SCRIPT_FILENAME'] = $worker;
++$env['PATH_TRANSLATED'] = $worker;
++$env['QUERY_STRING'] = '';
++$env['TEST_SESSION_RECORD'] = $record;
++$env['TEST_SESSION_FILE'] = $file;
++
++$pipes = array();
++$proc = proc_open(
++    $cmd,
++    array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')),
++    $pipes,
++    $dir,
++    $env
++);
++fwrite($pipes[0], $body);
++fclose($pipes[0]);
++$out = stream_get_contents($pipes[1]);
++$err = stream_get_contents($pipes[2]);
++fclose($pipes[1]);
++fclose($pipes[2]);
++proc_close($proc);
++
++echo $out;
++if ($err !== '') {
++    echo "--- stderr:\n$err";
++}
++
++?>
++--CLEAN--
++<?php
++$dir = __DIR__ . '/rfc1867_no_user_code';
++ at unlink("$dir/worker.php");
++foreach (glob("$dir/sess_*") as $f) {
++    unlink($f);
++}
++ at rmdir($dir);
++?>
++--EXPECTF--
++X-Powered-By: PHP/%s
++Content-type: text/html%A
++
++bool(false)
++bool(true)
++--- record after the update:
++bool(true)
++string(20) "upload_progress_key|"
++--- session_start():
++autoload(Missing)
++W::__wakeup
++string(17) "rfc1867nousercode"
++int(1)
++string(7) "payload"
++object(__PHP_Incomplete_Class)#%d (2) {
++  ["__PHP_Incomplete_Class_Name"]=>
++  string(7) "Missing"
++  ["x"]=>
++  int(3)
++}
++bool(true)
+diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
+--- a/ext/standard/basic_functions.c
++++ b/ext/standard/basic_functions.c
+@@ -3744,6 +3744,7 @@ PHP_RINIT_FUNCTION(basic) /* {{{ */
+ 	BG(serialize_lock) = 0;
+ 	memset(&BG(serialize), 0, sizeof(BG(serialize)));
+ 	memset(&BG(unserialize), 0, sizeof(BG(unserialize)));
++	BG(serialize_incomplete_classes_only) = 0;
+ 
+ 	BG(strtok_string) = NULL;
+ 	BG(strtok_zval) = NULL;
+@@ -3784,6 +3785,19 @@ PHP_RINIT_FUNCTION(basic) /* {{{ */
+ }
+ /* }}} */
+ 
++/* Called from php_request_startup(), before any request data is processed. */
++PHPAPI void php_basic_early_rinit(TSRMLS_D)
++{
++	/* The session upload progress handler (un)serializes the session and
++	 * registers the session id with the URL rewriter from
++	 * php_hash_environment(), before RINIT. */
++	BG(serialize_lock) = 0;
++	memset(&BG(serialize), 0, sizeof(BG(serialize)));
++	memset(&BG(unserialize), 0, sizeof(BG(unserialize)));
++	BG(serialize_incomplete_classes_only) = 0;
++	php_url_scanner_early_rinit(TSRMLS_C);
++}
++
+ PHP_RSHUTDOWN_FUNCTION(basic) /* {{{ */
+ {
+ 	if (BG(strtok_zval)) {
+diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h
+--- a/ext/standard/basic_functions.h
++++ b/ext/standard/basic_functions.h
+@@ -230,6 +230,9 @@ typedef struct _php_basic_globals {
+ #endif
+ 
+ 	int umask;
++
++	/* var.c, var_unserializer.re; last, so that the offsets extensions use do not move */
++	zend_bool serialize_incomplete_classes_only;
+ } php_basic_globals;
+ 
+ #ifdef ZTS
+@@ -257,6 +260,7 @@ typedef struct _php_shutdown_function_entry {
+ 	int arg_count;
+ } php_shutdown_function_entry;
+ 
++PHPAPI void php_basic_early_rinit(TSRMLS_D);
+ PHPAPI extern zend_bool register_user_shutdown_function(char *function_name, size_t function_len, php_shutdown_function_entry *shutdown_function_entry TSRMLS_DC);
+ PHPAPI extern zend_bool remove_user_shutdown_function(char *function_name, size_t function_len TSRMLS_DC);
+ PHPAPI extern zend_bool append_user_shutdown_function(php_shutdown_function_entry shutdown_function_entry TSRMLS_DC);
+diff --git a/ext/standard/php_incomplete_class.h b/ext/standard/php_incomplete_class.h
+--- a/ext/standard/php_incomplete_class.h
++++ b/ext/standard/php_incomplete_class.h
+@@ -52,6 +52,9 @@
+ 
+ #define INCOMPLETE_CLASS "__PHP_Incomplete_Class"
+ #define MAGIC_MEMBER "__PHP_Incomplete_Class_Name"
++/* Set by php_var_unserialize() in incomplete_classes_only mode: the raw payload
++ * of a C: value. The serializer writes it back unchanged. */
++#define MAGIC_MEMBER_SERIALIZED "__PHP_Incomplete_Class_Serialized"
+ 
+ #ifdef __cplusplus
+ extern "C" {
+diff --git a/ext/standard/php_var.h b/ext/standard/php_var.h
+--- a/ext/standard/php_var.h
++++ b/ext/standard/php_var.h
+@@ -51,6 +51,7 @@ typedef struct php_unserialize_data* php_unserialize_data_t;
+ 
+ PHPAPI void php_var_serialize(smart_str *buf, zval **struc, php_serialize_data_t *var_hash TSRMLS_DC);
+ PHPAPI int php_var_unserialize(zval **rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash TSRMLS_DC);
++PHPAPI void php_var_set_incomplete_classes_only(zend_bool incomplete_classes_only TSRMLS_DC);
+ 
+ #define PHP_VAR_SERIALIZE_INIT(var_hash_ptr) \
+ do  { \
+diff --git a/ext/standard/url_scanner_ex.c b/ext/standard/url_scanner_ex.c
+--- a/ext/standard/url_scanner_ex.c
++++ b/ext/standard/url_scanner_ex.c
+@@ -922,6 +922,12 @@ static int php_url_scanner_ex_deactivate(TSRMLS_D)
+ 	return SUCCESS;
+ }
+ 
++void php_url_scanner_early_rinit(TSRMLS_D)
++{
++	/* See php_basic_early_rinit() */
++	php_url_scanner_ex_activate(TSRMLS_C);
++}
++
+ static void php_url_scanner_output_handler(char *output, uint output_len, char **handled_output, uint *handled_output_len, int mode TSRMLS_DC)
+ {
+ 	size_t len;
+diff --git a/ext/standard/url_scanner_ex.h b/ext/standard/url_scanner_ex.h
+--- a/ext/standard/url_scanner_ex.h
++++ b/ext/standard/url_scanner_ex.h
+@@ -26,6 +26,7 @@ PHP_MSHUTDOWN_FUNCTION(url_scanner_ex);
+ 
+ PHP_RINIT_FUNCTION(url_scanner_ex);
+ PHP_RSHUTDOWN_FUNCTION(url_scanner_ex);
++void php_url_scanner_early_rinit(TSRMLS_D);
+ 
+ PHPAPI char *php_url_scanner_adapt_single_url(const char *url, size_t urllen, const char *name, const char *value, size_t *newlen TSRMLS_DC);
+ PHPAPI int php_url_scanner_add_var(char *name, int name_len, char *value, int value_len, int urlencode TSRMLS_DC);
+diff --git a/ext/standard/url_scanner_ex.re b/ext/standard/url_scanner_ex.re
+--- a/ext/standard/url_scanner_ex.re
++++ b/ext/standard/url_scanner_ex.re
+@@ -430,6 +430,12 @@ static int php_url_scanner_ex_deactivate(TSRMLS_D)
+ 	return SUCCESS;
+ }
+ 
++void php_url_scanner_early_rinit(TSRMLS_D)
++{
++	/* See php_basic_early_rinit() */
++	php_url_scanner_ex_activate(TSRMLS_C);
++}
++
+ static void php_url_scanner_output_handler(char *output, uint output_len, char **handled_output, uint *handled_output_len, int mode TSRMLS_DC)
+ {
+ 	size_t len;
+diff --git a/ext/standard/var.c b/ext/standard/var.c
+--- a/ext/standard/var.c
++++ b/ext/standard/var.c
+@@ -615,6 +615,38 @@ static inline zend_bool php_var_serialize_class_name(smart_str *buf, zval *struc
+ }
+ /* }}} */
+ 
++/* Write back the C: payload kept by php_var_unserialize() in incomplete_classes_only mode */
++static zend_bool php_var_serialize_incomplete_raw(smart_str *buf, zval *struc TSRMLS_DC) /* {{{ */
++{
++	zval **member;
++	char *class_name;
++	zend_uint name_len;
++
++	if (zend_hash_find(Z_OBJPROP_P(struc), MAGIC_MEMBER_SERIALIZED, sizeof(MAGIC_MEMBER_SERIALIZED), (void **) &member) == FAILURE
++			|| Z_TYPE_PP(member) != IS_STRING) {
++		/* Other incomplete classes are handled using the normal path */
++		return 0;
++	}
++
++	class_name = php_lookup_class_name(struc, &name_len);
++	if (!class_name) {
++		return 0;
++	}
++
++	smart_str_appendl(buf, "C:", 2);
++	smart_str_append_long(buf, (long) name_len);
++	smart_str_appendl(buf, ":\"", 2);
++	smart_str_appendl(buf, class_name, name_len);
++	smart_str_appendl(buf, "\":", 2);
++	smart_str_append_long(buf, (long) Z_STRLEN_PP(member));
++	smart_str_appendl(buf, ":{", 2);
++	smart_str_appendl(buf, Z_STRVAL_PP(member), Z_STRLEN_PP(member));
++	smart_str_appendc(buf, '}');
++	efree(class_name);
++	return 1;
++}
++/* }}} */
++
+ static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, HashTable *var_hash TSRMLS_DC) /* {{{ */
+ {
+ 	int count;
+@@ -771,6 +803,10 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, HashTable *var
+ 					ce = Z_OBJCE_P(struc);
+ 				}
+ 
++				if (ce == PHP_IC_ENTRY && BG(serialize_incomplete_classes_only) && php_var_serialize_incomplete_raw(buf, struc TSRMLS_CC)) {
++					return;
++				}
++
+ 				if (ce && ce->serialize != NULL) {
+ 					/* has custom handler */
+ 					unsigned char *serialized_data = NULL;
+@@ -910,6 +946,16 @@ PHPAPI void php_var_serialize(smart_str *buf, zval **struc, php_serialize_data_t
+ }
+ /* }}} */
+ 
++/* Represent every object as __PHP_Incomplete_Class, without resolving its class:
++ * no autoloading, no unserialize_callback_func, no __wakeup(). O: and C: values
++ * serialize back to the same bytes. A global rather than a var_hash member:
++ * extensions allocate var_hash through the PHP_VAR_*_INIT() macros. */
++PHPAPI void php_var_set_incomplete_classes_only(zend_bool incomplete_classes_only TSRMLS_DC) /* {{{ */
++{
++	BG(serialize_incomplete_classes_only) = incomplete_classes_only;
++}
++/* }}} */
++
+ /* {{{ proto string serialize(mixed variable)
+    Returns a string representation of variable (which can later be unserialized) */
+ PHP_FUNCTION(serialize)
+diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re
+--- a/ext/standard/var_unserializer.re
++++ b/ext/standard/var_unserializer.re
+@@ -341,6 +341,16 @@ static inline size_t parse_uiv(const unsigned char *p)
+ #define UNSERIALIZE_PARAMETER zval **rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash TSRMLS_DC
+ #define UNSERIALIZE_PASSTHRU rval, p, max, var_hash TSRMLS_CC
+ 
++/* The members that incomplete_classes_only mode writes itself: the class name of
++ * every object, and the payload kept for C: entries. Data that already carries
++ * one of them cannot be told apart from that bookkeeping, so it is rejected
++ * rather than silently overwritten. */
++static inline int is_incomplete_class_reserved_member(const char *name, int name_len)
++{
++	return (name_len == sizeof(MAGIC_MEMBER) - 1 && !memcmp(name, MAGIC_MEMBER, name_len))
++		|| (name_len == sizeof(MAGIC_MEMBER_SERIALIZED) - 1 && !memcmp(name, MAGIC_MEMBER_SERIALIZED, name_len));
++}
++
+ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long elements, int objprops)
+ {
+ 	while (elements-- > 0) {
+@@ -358,6 +368,13 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long
+ 			return 0;
+ 		}
+ 
++		if (objprops && UNEXPECTED(BG(serialize_incomplete_classes_only))
++				&& Z_TYPE_P(key) == IS_STRING
++				&& UNEXPECTED(is_incomplete_class_reserved_member(Z_STRVAL_P(key), Z_STRLEN_P(key)))) {
++			var_push_dtor_no_addref(var_hash, &key);
++			return 0;
++		}
++
+ 		ALLOC_INIT_ZVAL(data);
+ 
+ 		if (!php_var_unserialize(&data, p, max, var_hash TSRMLS_CC)) {
+@@ -412,7 +429,7 @@ static inline int finish_nested_data(UNSERIALIZE_PARAMETER)
+ 	return 1;
+ }
+ 
+-static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
++static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce, zend_bool keep_payload)
+ {
+ 	long datalen;
+ 
+@@ -425,7 +442,16 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
+ 		return 0;
+ 	}
+ 
+-	if (ce->unserialize == NULL) {
++	if (keep_payload) {
++		/* The class was not resolved (see php_var_set_incomplete_classes_only()):
++		 * keep the payload verbatim, so that the object serializes back to the same bytes. */
++		zval *payload;
++
++		object_init_ex(*rval, ce);
++		MAKE_STD_ZVAL(payload);
++		ZVAL_STRINGL(payload, (char *) *p, datalen, 1);
++		zend_hash_update(Z_OBJPROP_PP(rval), MAGIC_MEMBER_SERIALIZED, sizeof(MAGIC_MEMBER_SERIALIZED), &payload, sizeof(payload), NULL);
++	} else if (ce->unserialize == NULL) {
+ 		zend_error(E_WARNING, "Class %s has no unserializer", ce->name);
+ 		object_init_ex(*rval, ce);
+ 	} else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash TSRMLS_CC) != SUCCESS) {
+@@ -782,6 +808,13 @@ object ":" uiv ":" ["]	{
+ 	class_name = estrndup(class_name, len);
+ 
+ 	do {
++		if (BG(serialize_incomplete_classes_only)) {
++			/* Do not look the class up at all, so that no user code can run. */
++			incomplete_class = 1;
++			ce = PHP_IC_ENTRY;
++			break;
++		}
++
+ 		/* Try to find class directly */
+ 		BG(serialize_lock)++;
+ 		if (zend_lookup_class(class_name, len2, &pce TSRMLS_CC) == SUCCESS) {
+@@ -859,7 +892,7 @@ object ":" uiv ":" ["]	{
+ 	if (custom_object) {
+ 		int ret;
+ 
+-		ret = object_custom(UNSERIALIZE_PASSTHRU, ce);
++		ret = object_custom(UNSERIALIZE_PASSTHRU, ce, BG(serialize_incomplete_classes_only));
  
+ 		if (ret && incomplete_class) {
+ 			php_store_class_name(*rval, class_name, len2);
 diff --git a/main/SAPI.c b/main/SAPI.c
 --- a/main/SAPI.c
 +++ b/main/SAPI.c
@@ -441,7 +1135,15 @@ diff --git a/main/SAPI.c b/main/SAPI.c
 diff --git a/main/main.c b/main/main.c
 --- a/main/main.c
 +++ b/main/main.c
-@@ -1699,6 +1699,8 @@ void php_request_shutdown_for_hook(void *dummy)
+@@ -1560,6 +1560,7 @@ int php_request_startup(TSRMLS_D)
+ 		PG(in_user_include) = 0;
+ 
+ 		zend_activate(TSRMLS_C);
++		php_basic_early_rinit(TSRMLS_C);
+ 		sapi_activate(TSRMLS_C);
+ 
+ #ifdef ZEND_SIGNALS
+@@ -1699,6 +1700,8 @@ void php_request_shutdown_for_hook(void *dummy)
  		for (i = 0; i < NUM_TRACK_VARS; i++) {
  			if (PG(http_globals)[i]) {
  				zval_ptr_dtor(&PG(http_globals)[i]);
@@ -450,7 +1152,7 @@ diff --git a/main/main.c b/main/main.c
  			}
  		}
  	} zend_end_try();
-@@ -1795,6 +1797,8 @@ void php_request_shutdown(void *dummy)
+@@ -1795,6 +1798,8 @@ void php_request_shutdown(void *dummy)
  		for (i=0; i<NUM_TRACK_VARS; i++) {
  			if (PG(http_globals)[i]) {
  				zval_ptr_dtor(&PG(http_globals)[i]);
@@ -459,7 +1161,7 @@ diff --git a/main/main.c b/main/main.c
  			}
  		}
  	} zend_end_try();
-@@ -1849,6 +1853,9 @@ void php_request_shutdown(void *dummy)
+@@ -1849,6 +1854,9 @@ void php_request_shutdown(void *dummy)
  #ifdef HAVE_DTRACE
  	DTRACE_REQUEST_SHUTDOWN(SAFE_FILENAME(SG(request_info).path_translated), SAFE_FILENAME(SG(request_info).request_uri), (char *)SAFE_FILENAME(SG(request_info).request_method));
  #endif /* HAVE_DTRACE */
diff --git a/php-ghsa-www2-q4fc-65wf.patch b/php-ghsa-www2-q4fc-65wf.patch
index 1c9e051..3ca047c 100644
--- a/php-ghsa-www2-q4fc-65wf.patch
+++ b/php-ghsa-www2-q4fc-65wf.patch
@@ -15,7 +15,7 @@ Source: PLD php PHP_5_6 branch, https://git.pld-linux.org/?p=packages/php.git;a=
 diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
 --- a/ext/standard/basic_functions.c
 +++ b/ext/standard/basic_functions.c
-@@ -3911,7 +3911,7 @@ PHP_NAMED_FUNCTION(php_inet_pton)
+@@ -3925,7 +3925,7 @@ PHP_NAMED_FUNCTION(php_inet_pton)
  	int address_len;
  	char buffer[17];
  
@@ -24,7 +24,7 @@ diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
  		RETURN_FALSE;
  	}
  
-@@ -3951,7 +3951,7 @@ PHP_FUNCTION(ip2long)
+@@ -3965,7 +3965,7 @@ PHP_FUNCTION(ip2long)
  	unsigned long int ip;
  #endif
  
@@ -33,7 +33,7 @@ diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
  		return;
  	}
  
-@@ -5622,7 +5622,7 @@ PHP_FUNCTION(getservbyname)
+@@ -5636,7 +5636,7 @@ PHP_FUNCTION(getservbyname)
  	int name_len, proto_len;
  	struct servent *serv;
  
@@ -42,7 +42,7 @@ diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
  		return;
  	}
  
-@@ -5656,7 +5656,7 @@ PHP_FUNCTION(getservbyport)
+@@ -5670,7 +5670,7 @@ PHP_FUNCTION(getservbyport)
  	long port;
  	struct servent *serv;
  
@@ -51,7 +51,7 @@ diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
  		return;
  	}
  
-@@ -5680,7 +5680,7 @@ PHP_FUNCTION(getprotobyname)
+@@ -5694,7 +5694,7 @@ PHP_FUNCTION(getprotobyname)
  	int name_len;
  	struct protoent *ent;
  
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/php.git/commitdiff/01f2cd56517ea1eca6960f5aff868297383a748f



More information about the pld-cvs-commit mailing list