Skip to content

Commit ebfd93f

Browse files
committed
Fixed bug #73483 (Segmentation fault on pcre_replace_callback)
1 parent 274951a commit ebfd93f

File tree

3 files changed

+84
-35
lines changed

3 files changed

+84
-35
lines changed

‎NEWS

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ PHP NEWS
1717
This may be enabled again using envirionment variable USE_ZEND_DTRACE=1.
1818
(Dmitry)
1919

20-
- Mysqlnd:
21-
. Fixed bug #64526 (Add missing mysqlnd.* parameters to php.ini-*). (cmb)
22-
20+
- Mysqlnd:
21+
. Fixed bug #64526 (Add missing mysqlnd.* parameters to php.ini-*). (cmb)
22+
2323
- ODBC:
2424
. Fixed bug #73448 (odbc_errormsg returns trash, always 513 bytes).
2525
(Anatol)
@@ -29,6 +29,7 @@ PHP NEWS
2929
. Fixed bug #73546 (Logging for opcache has an empty file name). (mhagstrand)
3030

3131
- PCRE:
32+
. Fixed bug #73483 (Segmentation fault on pcre_replace_callback). (Laruence)
3233
. Fixed bug #73392 (A use-after-free in zend allocator management).
3334
(Laruence)
3435

‎ext/pcre/php_pcre.c

+65-32
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,6 @@ static void php_free_pcre_cache(zval *data) /* {{{ */
114114
}
115115
#if HAVE_SETLOCALE
116116
if ((void*)pce->tables) pefree((void*)pce->tables, 1);
117-
if (pce->locale) {
118-
zend_string_release(pce->locale);
119-
}
120117
#endif
121118
pefree(pce, 1);
122119
}
@@ -320,27 +317,30 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
320317
pcre_cache_entry *pce;
321318
pcre_cache_entry new_entry;
322319
int rc;
320+
zend_string *key;
321+
322+
#if HAVE_SETLOCALE
323+
if (BG(locale_string) &&
324+
(ZSTR_LEN(BG(locale_string)) != 1 && ZSTR_VAL(BG(locale_string))[0] != 'C')) {
325+
key = zend_string_alloc(ZSTR_LEN(regex) + ZSTR_LEN(BG(locale_string)) + 1, 0);
326+
memcpy(ZSTR_VAL(key), ZSTR_VAL(BG(locale_string)), ZSTR_LEN(BG(locale_string)) + 1);
327+
memcpy(ZSTR_VAL(key) + ZSTR_LEN(BG(locale_string)), ZSTR_VAL(regex), ZSTR_LEN(regex) + 1);
328+
} else
329+
#endif
330+
{
331+
key = regex;
332+
}
323333

324334
/* Try to lookup the cached regex entry, and if successful, just pass
325335
back the compiled pattern, otherwise go on and compile it. */
326-
pce = zend_hash_find_ptr(&PCRE_G(pcre_cache), regex);
336+
pce = zend_hash_find_ptr(&PCRE_G(pcre_cache), key);
327337
if (pce) {
328338
#if HAVE_SETLOCALE
329-
if (pce->locale == BG(locale_string) ||
330-
(pce->locale && BG(locale_string) &&
331-
ZSTR_LEN(pce->locale) == ZSTR_LEN(BG(locale_string)) &&
332-
!memcmp(ZSTR_VAL(pce->locale), ZSTR_VAL(BG(locale_string)), ZSTR_LEN(pce->locale))) ||
333-
(!pce->locale &&
334-
ZSTR_LEN(BG(locale_string)) == 1 &&
335-
ZSTR_VAL(BG(locale_string))[0] == 'C') ||
336-
(!BG(locale_string) &&
337-
ZSTR_LEN(pce->locale) == 1 &&
338-
ZSTR_VAL(pce->locale)[0] == 'C')) {
339-
return pce;
339+
if (key != regex) {
340+
zend_string_release(key);
340341
}
341-
#else
342-
return pce;
343342
#endif
343+
return pce;
344344
}
345345

346346
p = ZSTR_VAL(regex);
@@ -349,6 +349,11 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
349349
get to the end without encountering a delimiter. */
350350
while (isspace((int)*(unsigned char *)p)) p++;
351351
if (*p == 0) {
352+
#if HAVE_SETLOCALE
353+
if (key != regex) {
354+
zend_string_release(key);
355+
}
356+
#endif
352357
php_error_docref(NULL, E_WARNING,
353358
p < ZSTR_VAL(regex) + ZSTR_LEN(regex) ? "Null byte in regex" : "Empty regular expression");
354359
return NULL;
@@ -358,6 +363,11 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
358363
or a backslash. */
359364
delimiter = *p++;
360365
if (isalnum((int)*(unsigned char *)&delimiter) || delimiter == '\\') {
366+
#if HAVE_SETLOCALE
367+
if (key != regex) {
368+
zend_string_release(key);
369+
}
370+
#endif
361371
php_error_docref(NULL,E_WARNING, "Delimiter must not be alphanumeric or backslash");
362372
return NULL;
363373
}
@@ -397,6 +407,11 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
397407
}
398408

399409
if (*pp == 0) {
410+
#if HAVE_SETLOCALE
411+
if (key != regex) {
412+
zend_string_release(key);
413+
}
414+
#endif
400415
if (pp < ZSTR_VAL(regex) + ZSTR_LEN(regex)) {
401416
php_error_docref(NULL,E_WARNING, "Null byte in regex");
402417
} else if (start_delimiter == end_delimiter) {
@@ -453,13 +468,17 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
453468
php_error_docref(NULL,E_WARNING, "Null byte in regex");
454469
}
455470
efree(pattern);
471+
#if HAVE_SETLOCALE
472+
if (key != regex) {
473+
zend_string_release(key);
474+
}
475+
#endif
456476
return NULL;
457477
}
458478
}
459479

460480
#if HAVE_SETLOCALE
461-
if (BG(locale_string) &&
462-
(ZSTR_LEN(BG(locale_string)) != 1 || ZSTR_VAL(BG(locale_string))[0] != 'C')) {
481+
if (key != regex) {
463482
tables = pcre_maketables();
464483
}
465484
#endif
@@ -472,6 +491,11 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
472491
tables);
473492

474493
if (re == NULL) {
494+
#if HAVE_SETLOCALE
495+
if (key != regex) {
496+
zend_string_release(key);
497+
}
498+
#endif
475499
php_error_docref(NULL,E_WARNING, "Compilation failed: %s at offset %d", error, erroffset);
476500
efree(pattern);
477501
if (tables) {
@@ -516,7 +540,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
516540
* these are supposedly the oldest ones (but not necessarily the least used
517541
* ones).
518542
*/
519-
if (zend_hash_num_elements(&PCRE_G(pcre_cache)) == PCRE_CACHE_SIZE) {
543+
if (!pce && zend_hash_num_elements(&PCRE_G(pcre_cache)) == PCRE_CACHE_SIZE) {
520544
int num_clean = PCRE_CACHE_SIZE / 8;
521545
zend_hash_apply_with_argument(&PCRE_G(pcre_cache), pcre_clean_cache, &num_clean);
522546
}
@@ -527,23 +551,29 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
527551
new_entry.preg_options = poptions;
528552
new_entry.compile_options = coptions;
529553
#if HAVE_SETLOCALE
530-
new_entry.locale = BG(locale_string) ?
531-
((GC_FLAGS(BG(locale_string)) & IS_STR_PERSISTENT) ?
532-
zend_string_copy(BG(locale_string)) :
533-
zend_string_init(ZSTR_VAL(BG(locale_string)), ZSTR_LEN(BG(locale_string)), 1)) :
534-
NULL;
554+
new_entry.locale = NULL;
535555
new_entry.tables = tables;
536556
#endif
537557
new_entry.refcount = 0;
538558

539559
rc = pcre_fullinfo(re, extra, PCRE_INFO_CAPTURECOUNT, &new_entry.capture_count);
540560
if (rc < 0) {
561+
#if HAVE_SETLOCALE
562+
if (key != regex) {
563+
zend_string_release(key);
564+
}
565+
#endif
541566
php_error_docref(NULL, E_WARNING, "Internal pcre_fullinfo() error %d", rc);
542567
return NULL;
543568
}
544569

545570
rc = pcre_fullinfo(re, extra, PCRE_INFO_NAMECOUNT, &new_entry.name_count);
546571
if (rc < 0) {
572+
#if HAVE_SETLOCALE
573+
if (key != regex) {
574+
zend_string_release(key);
575+
}
576+
#endif
547577
php_error_docref(NULL, E_WARNING, "Internal pcre_fullinfo() error %d", rc);
548578
return NULL;
549579
}
@@ -556,15 +586,18 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
556586
* as hash keys especually for this table.
557587
* See bug #63180
558588
*/
559-
if (!ZSTR_IS_INTERNED(regex) || !(GC_FLAGS(regex) & IS_STR_PERMANENT)) {
560-
zend_string *str = zend_string_init(ZSTR_VAL(regex), ZSTR_LEN(regex), 1);
561-
GC_REFCOUNT(str) = 0; /* will be incremented by zend_hash_update_mem() */
562-
ZSTR_H(str) = ZSTR_H(regex);
563-
regex = str;
589+
if (!ZSTR_IS_INTERNED(key) || !(GC_FLAGS(key) & IS_STR_PERMANENT)) {
590+
pce = zend_hash_str_update_mem(&PCRE_G(pcre_cache),
591+
ZSTR_VAL(key), ZSTR_LEN(key), &new_entry, sizeof(pcre_cache_entry));
592+
#if HAVE_SETLOCALE
593+
if (key != regex) {
594+
zend_string_release(key);
595+
}
596+
#endif
597+
} else {
598+
pce = zend_hash_update_mem(&PCRE_G(pcre_cache), key, &new_entry, sizeof(pcre_cache_entry));
564599
}
565600

566-
pce = zend_hash_update_mem(&PCRE_G(pcre_cache), regex, &new_entry, sizeof(pcre_cache_entry));
567-
568601
return pce;
569602
}
570603
/* }}} */

‎ext/pcre/tests/bug73483.phpt

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Bug #73483 (Segmentation fault on pcre_replace_callback)
3+
--FILE--
4+
<?php
5+
$regex = "#dummy#";
6+
setlocale(LC_ALL, "C");
7+
var_dump(preg_replace_callback($regex, function (array $matches) use($regex) {
8+
setlocale(LC_ALL, "en_US");
9+
$ret = preg_replace($regex, "okey", $matches[0]);
10+
setlocale(LC_ALL, "C");
11+
return $ret;
12+
}, "dummy"));
13+
?>
14+
--EXPECT--
15+
string(4) "okey"

0 commit comments

Comments
 (0)