Skip to content

Commit 746a684

Browse files
committed
Merge branch 'PHP-8.6'
* PHP-8.6: Fix GH-24438: Preserve indirect types in JIT side traces Fix GH-17733: System entropy does not take into account function order, resulting in wrong functions possibly called in different configurations ext/openssl: Fix build with OpenSSL 4.1 (#24686)
2 parents b7567e5 + ca0030b commit 746a684

13 files changed

Lines changed: 213 additions & 40 deletions

‎Zend/zend_API.c‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
#include "zend_enum.h"
3535
#include "zend_object_handlers.h"
3636
#include "zend_observer.h"
37-
#include "zend_system_id.h"
3837

3938
#include <stdarg.h>
4039

@@ -3229,7 +3228,6 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
32293228
}
32303229
zend_flf_handlers[zend_flf_count] = flf_info->handler;
32313230
zend_flf_functions[zend_flf_count] = (zend_function *)reg_function;
3232-
zend_add_system_entropy("frameless", ZSTR_VAL(reg_function->function_name), &flf_info->num_args, sizeof(flf_info->num_args));
32333231
zend_flf_count++;
32343232
flf_info++;
32353233
}

‎Zend/zend_system_id.c‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ void zend_finalize_system_id(void)
8787
}
8888
}
8989

90+
/* Cached scripts can refer to offsets of internal functions.
91+
* Effects of disable_functions and frameless functions must be taken into account.
92+
* Therefore, embed the function names in-order.
93+
* Updating third party extensions may change function signatures, arginfo, offsets, ...
94+
* Therefore, also embed the loaded modules and their versions. */
95+
ZEND_HASH_MAP_FOREACH_STR_KEY(CG(function_table), zend_string *name) {
96+
PHP_MD5Update(&context, ZSTR_VAL(name), ZSTR_LEN(name) + 1);
97+
} ZEND_HASH_FOREACH_END();
98+
ZEND_HASH_MAP_FOREACH_PTR(&module_registry, zend_module_entry *mod) {
99+
PHP_MD5Update(&context, mod->name, strlen(mod->name) + 1);
100+
if (EXPECTED(mod->version != NO_VERSION_YET)) {
101+
PHP_MD5Update(&context, mod->version, strlen(mod->version) + 1);
102+
}
103+
} ZEND_HASH_FOREACH_END();
104+
90105
PHP_MD5Final(digest, &context);
91106
zend_bin2hex(zend_system_id, digest, sizeof digest);
92107
finalized = 1;

‎ext/opcache/jit/zend_jit_trace.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1774,7 +1774,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
17741774
if (i < op_array->last_var) {
17751775
ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
17761776
} else {
1777-
ssa_var_info[i].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
1777+
ssa_var_info[i].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_INDIRECT | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
17781778
}
17791779
}
17801780
if (i < parent_vars_count) {

‎ext/opcache/tests/gh17733.phpt‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
--TEST--
2+
GH-17733 (System entropy does not take into account function order, resulting in wrong functions possibly called in different configurations)
3+
--EXTENSIONS--
4+
opcache
5+
--FILE--
6+
<?php
7+
$cache = __DIR__ . '/' . 'gh17733';
8+
$script = $cache . '/' . 'call.php';
9+
mkdir($cache);
10+
11+
function write_script(string $script, int $version): void {
12+
file_put_contents($script, <<<PHP
13+
<?php
14+
echo "v$version\n";
15+
try {
16+
var_dump(str_repeat("ab", 3));
17+
} catch (\Throwable \$e) {
18+
echo \$e::class, ": ", \$e->getMessage(), "\n";
19+
}
20+
PHP);
21+
}
22+
23+
function run(string $cache, string $script, string $disable_functions): void {
24+
$ext = ini_get('extension_dir');
25+
$opcache = PHP_OS_FAMILY === 'Windows' ? 'php_opcache.dll' : 'opcache.so';
26+
$cmd = [PHP_BINARY, '-n', '-d', 'extension_dir="' . $ext . '"'];
27+
if (file_exists($ext . '/' . $opcache)) {
28+
array_push($cmd, '-d', 'zend_extension=opcache');
29+
}
30+
array_push($cmd,
31+
'-d', 'opcache.enable_cli=1',
32+
'-d', 'opcache.file_cache="' . $cache . '"',
33+
'-d', 'opcache.file_cache_only=1',
34+
'-d', 'opcache.file_update_protection=0',
35+
'-d', 'opcache.validate_timestamps=0', // Important to tell apart versions
36+
'-d', 'disable_functions=' . $disable_functions,
37+
$script,
38+
);
39+
$proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes);
40+
echo stream_get_contents($pipes[1]);
41+
proc_close($proc);
42+
}
43+
44+
function system_ids(string $cache): int {
45+
$pattern = PHP_OS_FAMILY === 'Windows' ? '/*/*' : '/*';
46+
return count(glob($cache . $pattern, GLOB_ONLYDIR));
47+
}
48+
49+
write_script($script, 1);
50+
run($cache, $script, '');
51+
var_dump(system_ids($cache));
52+
53+
write_script($script, 2);
54+
echo "--- Same environment reuses the cache ---\n";
55+
run($cache, $script, '');
56+
var_dump(system_ids($cache));
57+
58+
echo "--- Shifted function table ---\n";
59+
run($cache, $script, 'zend_version');
60+
var_dump(system_ids($cache));
61+
62+
echo "--- Disabled function ---\n";
63+
run($cache, $script, 'str_repeat');
64+
var_dump(system_ids($cache));
65+
66+
write_script($script, 3);
67+
echo "--- Each environment reuses its own cache ---\n";
68+
run($cache, $script, 'zend_version');
69+
run($cache, $script, 'str_repeat');
70+
run($cache, $script, '');
71+
var_dump(system_ids($cache));
72+
?>
73+
--CLEAN--
74+
<?php
75+
$cache = __DIR__ . '/' . 'gh17733';
76+
if (is_dir($cache)) {
77+
$it = new RecursiveIteratorIterator(
78+
new RecursiveDirectoryIterator($cache, FilesystemIterator::SKIP_DOTS),
79+
RecursiveIteratorIterator::CHILD_FIRST
80+
);
81+
foreach ($it as $file) {
82+
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
83+
}
84+
rmdir($cache);
85+
}
86+
?>
87+
--EXPECT--
88+
v1
89+
string(6) "ababab"
90+
int(1)
91+
--- Same environment reuses the cache ---
92+
v1
93+
string(6) "ababab"
94+
int(1)
95+
--- Shifted function table ---
96+
v2
97+
string(6) "ababab"
98+
int(2)
99+
--- Disabled function ---
100+
v2
101+
Error: Call to undefined function str_repeat()
102+
int(3)
103+
--- Each environment reuses its own cache ---
104+
v2
105+
string(6) "ababab"
106+
v2
107+
Error: Call to undefined function str_repeat()
108+
v1
109+
string(6) "ababab"
110+
int(3)

‎ext/opcache/tests/jit/gh24438.phpt‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-24438 (Tracing JIT preserves indirect arguments on side-trace entry)
3+
--EXTENSIONS--
4+
opcache
5+
--INI--
6+
opcache.enable=1
7+
opcache.enable_cli=1
8+
opcache.file_update_protection=0
9+
opcache.jit_buffer_size=64M
10+
opcache.jit=tracing
11+
opcache.jit_hot_loop=1
12+
opcache.jit_hot_func=1
13+
opcache.jit_hot_return=1
14+
opcache.jit_hot_side_exit=1
15+
opcache.jit_max_polymorphic_calls=0
16+
--FILE--
17+
<?php
18+
function byVal($value) {
19+
$value = (int) $value + 1000;
20+
return $value;
21+
}
22+
23+
function byRef(&$value) {
24+
$value = (int) $value + 1;
25+
return $value;
26+
}
27+
28+
$fns = ['byVal', 'byRef'];
29+
$arr = [10, 20];
30+
for ($i = 0; $i < 120; $i++) {
31+
$f = $fns[$i % 2];
32+
$f($arr[1]);
33+
$f($arr[0]);
34+
}
35+
36+
var_dump($arr[0], $arr[1]);
37+
?>
38+
--EXPECT--
39+
int(70)
40+
int(80)

‎ext/opcache/zend_file_cache.c‎

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -583,32 +583,13 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra
583583
}
584584
if (opline->op2_type == IS_CONST) {
585585
SERIALIZE_PTR(opline->op2.zv);
586-
587-
/* See GH-17733. Reset Z_EXTRA_P(op2) of ZEND_INIT_FCALL, which
588-
* is an offset into the global function table, to avoid calling
589-
* incorrect functions when environment changes. This, and the
590-
* equivalent code below, can be removed once proper system ID
591-
* validation is implemented. */
592-
if (opline->opcode == ZEND_INIT_FCALL) {
593-
zval *op2 = opline->op2.zv;
594-
UNSERIALIZE_PTR(op2);
595-
Z_EXTRA_P(op2) = 0;
596-
ZEND_VM_SET_OPCODE_HANDLER(opline);
597-
}
598586
}
599587
#else
600588
if (opline->op1_type == IS_CONST) {
601589
opline->op1.constant = RT_CONSTANT(opline, opline->op1) - literals;
602590
}
603591
if (opline->op2_type == IS_CONST) {
604-
zval *op2 = RT_CONSTANT(opline, opline->op2);
605-
opline->op2.constant = op2 - literals;
606-
607-
/* See GH-17733 and comment above. */
608-
if (opline->opcode == ZEND_INIT_FCALL) {
609-
Z_EXTRA_P(op2) = 0;
610-
ZEND_VM_SET_OPCODE_HANDLER(opline);
611-
}
592+
opline->op2.constant = RT_CONSTANT(opline, opline->op2) - literals;
612593
}
613594
#endif
614595
#if ZEND_USE_ABS_JMP_ADDR

‎ext/openssl/openssl.c‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ PHP_FUNCTION(openssl_spki_new)
10181018
}
10191019

10201020
if (challenge) {
1021-
if (!ASN1_STRING_set(spki->spkac->challenge, challenge, (int)challenge_len)) {
1021+
if (!ASN1_STRING_set1_data(spki->spkac->challenge, (const unsigned char *) challenge, challenge_len)) {
10221022
php_openssl_store_errors();
10231023
php_error_docref(NULL, E_WARNING, "Unable to set challenge data");
10241024
goto cleanup;
@@ -1205,7 +1205,8 @@ PHP_FUNCTION(openssl_spki_export_challenge)
12051205
goto cleanup;
12061206
}
12071207

1208-
RETVAL_STRING((const char *)ASN1_STRING_get0_data(spki->spkac->challenge));
1208+
RETVAL_STRINGL_FAST((const char *)ASN1_STRING_get0_data(spki->spkac->challenge),
1209+
ASN1_STRING_get_length(spki->spkac->challenge));
12091210
goto cleanup;
12101211

12111212
cleanup:

‎ext/openssl/openssl_backend_common.c‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_NAME *
7272
} else {
7373
/* ASN1_STRING_get0_data(3): Since this is an internal pointer it should not be freed or modified in any way */
7474
to_add = ASN1_STRING_get0_data(str);
75-
to_add_len = ASN1_STRING_length(str);
75+
to_add_len = ASN1_STRING_get_length(str);
7676
}
7777

7878
if (to_add_len != -1) {
@@ -107,7 +107,7 @@ void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_NAME *
107107

108108
void php_openssl_add_assoc_asn1_string(zval * val, char * key, const ASN1_STRING * str)
109109
{
110-
add_assoc_stringl(val, key, (const char *)ASN1_STRING_get0_data(str), ASN1_STRING_length(str));
110+
add_assoc_stringl(val, key, (const char *)ASN1_STRING_get0_data(str), ASN1_STRING_get_length(str));
111111
}
112112

113113
time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr)
@@ -123,6 +123,7 @@ time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr)
123123
struct tm thetime;
124124
char * strbuf;
125125
char * thestr;
126+
const char *timestr_data;
126127
long gmadjust = 0;
127128
size_t timestr_len;
128129

@@ -131,24 +132,25 @@ time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr)
131132
return (time_t)-1;
132133
}
133134

134-
timestr_len = (size_t)ASN1_STRING_length(timestr);
135+
timestr_len = ASN1_STRING_get_length(timestr);
136+
timestr_data = timestr_len ? (const char *)ASN1_STRING_get0_data(timestr) : "";
135137

136-
if (timestr_len != strlen((const char *)ASN1_STRING_get0_data(timestr))) {
138+
if (memchr(timestr_data, '\0', timestr_len) != NULL) {
137139
php_error_docref(NULL, E_WARNING, "Illegal length in timestamp");
138140
return (time_t)-1;
139141
}
140142

141143
if (timestr_len < 13) {
142-
php_error_docref(NULL, E_WARNING, "Unable to parse time string %s correctly", ASN1_STRING_get0_data(timestr));
144+
php_error_docref(NULL, E_WARNING, "Unable to parse time string %.*s correctly", (int)timestr_len, timestr_data);
143145
return (time_t)-1;
144146
}
145147

146148
if (ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME && timestr_len < 15) {
147-
php_error_docref(NULL, E_WARNING, "Unable to parse time string %s correctly", ASN1_STRING_get0_data(timestr));
149+
php_error_docref(NULL, E_WARNING, "Unable to parse time string %.*s correctly", (int)timestr_len, timestr_data);
148150
return (time_t)-1;
149151
}
150152

151-
strbuf = estrdup((const char *)ASN1_STRING_get0_data(timestr));
153+
strbuf = estrndup(timestr_data, timestr_len);
152154

153155
memset(&thetime, 0, sizeof(thetime));
154156

@@ -636,7 +638,7 @@ int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *extensio
636638

637639
extension_data = X509_EXTENSION_get_data(extension);
638640
p = ASN1_STRING_get0_data(extension_data);
639-
length = ASN1_STRING_length(extension_data);
641+
length = ASN1_STRING_get_length(extension_data);
640642
if (method->it) {
641643
names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL, &p, length,
642644
ASN1_ITEM_ptr(method->it)));
@@ -658,19 +660,19 @@ int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *extensio
658660
BIO_puts(bio, "email:");
659661
as = name->d.rfc822Name;
660662
BIO_write(bio, ASN1_STRING_get0_data(as),
661-
ASN1_STRING_length(as));
663+
ASN1_STRING_get_length(as));
662664
break;
663665
case GEN_DNS:
664666
BIO_puts(bio, "DNS:");
665667
as = name->d.dNSName;
666668
BIO_write(bio, ASN1_STRING_get0_data(as),
667-
ASN1_STRING_length(as));
669+
ASN1_STRING_get_length(as));
668670
break;
669671
case GEN_URI:
670672
BIO_puts(bio, "URI:");
671673
as = name->d.uniformResourceIdentifier;
672674
BIO_write(bio, ASN1_STRING_get0_data(as),
673-
ASN1_STRING_length(as));
675+
ASN1_STRING_get_length(as));
674676
break;
675677
default:
676678
/* use builtin print for GEN_OTHERNAME, GEN_X400,

‎ext/openssl/php_openssl_backend.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
#include <openssl/pkcs12.h>
3636
#include <openssl/cms.h>
3737

38+
#if OPENSSL_VERSION_NUMBER < 0x40100000L
39+
#define ASN1_STRING_set1_data(str, data, len) ASN1_STRING_set((str), (data), (int) (len))
40+
#define ASN1_STRING_get_length(str) ((size_t) ASN1_STRING_length(str))
41+
#endif
42+
3843
#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 4
3944
typedef const X509_EXTENSION PHP_OPENSSL_X509_EXTENSION;
4045
#else
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
openssl_spki_export_challenge() preserves empty challenges and embedded NUL bytes
3+
--EXTENSIONS--
4+
openssl
5+
--FILE--
6+
<?php
7+
$key = openssl_pkey_get_private('file://' . __DIR__ . '/private_rsa_2048.key');
8+
9+
foreach (["", "challenge", "\0challenge", "challenge\0", "left\0right"] as $challenge) {
10+
$spkac = openssl_spki_new($key, $challenge, OPENSSL_ALGO_SHA256);
11+
var_dump(openssl_spki_export_challenge(substr($spkac, 6)) === $challenge);
12+
}
13+
?>
14+
--EXPECT--
15+
bool(true)
16+
bool(true)
17+
bool(true)
18+
bool(true)
19+
bool(true)

0 commit comments

Comments
 (0)