Skip to content

Commit 4a4ae45

Browse files
committed
Fix bug #81142 by adding zend_string_init_existing_interned()
Add a new interned string handler that fetches an interned string if it exists, but does not create one if it does not (and instead returns a non-interned string). This fixes bug #81142, by preventing the creating of new interned strings for unserialized array keys. Closes GH-7360.
1 parent c39332d commit 4a4ae45

File tree

6 files changed

+77
-5
lines changed

6 files changed

+77
-5
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ PHP NEWS
3434
- SNMP:
3535
. Implement SHA256 and SHA512 for security protocol. (remi)
3636

37+
- Standard:
38+
. Fixed bug #81142 (PHP 7.3+ memory leak when unserialize() is used on an
39+
associative array). (Nikita)
40+
3741
05 Aug 2021, PHP 8.1.0beta2
3842

3943
- Core:

Zend/zend_string.c

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@
2525

2626
ZEND_API zend_new_interned_string_func_t zend_new_interned_string;
2727
ZEND_API zend_string_init_interned_func_t zend_string_init_interned;
28+
ZEND_API zend_string_init_existing_interned_func_t zend_string_init_existing_interned;
2829

2930
static zend_string* ZEND_FASTCALL zend_new_interned_string_permanent(zend_string *str);
3031
static zend_string* ZEND_FASTCALL zend_new_interned_string_request(zend_string *str);
3132
static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char *str, size_t size, bool permanent);
33+
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent);
3234
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent);
35+
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent);
3336

3437
/* Any strings interned in the startup phase. Common to all the threads,
3538
won't be free'd until process exit. If we want an ability to
@@ -39,6 +42,7 @@ static HashTable interned_strings_permanent;
3942

4043
static zend_new_interned_string_func_t interned_string_request_handler = zend_new_interned_string_request;
4144
static zend_string_init_interned_func_t interned_string_init_request_handler = zend_string_init_interned_request;
45+
static zend_string_init_existing_interned_func_t interned_string_init_existing_request_handler = zend_string_init_existing_interned_request;
4246

4347
ZEND_API zend_string *zend_empty_string = NULL;
4448
ZEND_API zend_string *zend_one_char_string[256];
@@ -83,6 +87,7 @@ ZEND_API void zend_interned_strings_init(void)
8387

8488
interned_string_request_handler = zend_new_interned_string_request;
8589
interned_string_init_request_handler = zend_string_init_interned_request;
90+
interned_string_init_existing_request_handler = zend_string_init_existing_interned_request;
8691

8792
zend_empty_string = NULL;
8893
zend_known_strings = NULL;
@@ -91,6 +96,7 @@ ZEND_API void zend_interned_strings_init(void)
9196

9297
zend_new_interned_string = zend_new_interned_string_permanent;
9398
zend_string_init_interned = zend_string_init_interned_permanent;
99+
zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
94100

95101
/* interned empty string */
96102
str = zend_string_alloc(sizeof("")-1, 1);
@@ -267,6 +273,20 @@ static zend_string* ZEND_FASTCALL zend_string_init_interned_permanent(const char
267273
return zend_add_interned_string(ret, &interned_strings_permanent, IS_STR_PERMANENT);
268274
}
269275

276+
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_permanent(const char *str, size_t size, bool permanent)
277+
{
278+
zend_ulong h = zend_inline_hash_func(str, size);
279+
zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
280+
if (ret) {
281+
return ret;
282+
}
283+
284+
ZEND_ASSERT(permanent);
285+
ret = zend_string_init(str, size, permanent);
286+
ZSTR_H(ret) = h;
287+
return ret;
288+
}
289+
270290
static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *str, size_t size, bool permanent)
271291
{
272292
zend_string *ret;
@@ -297,6 +317,25 @@ static zend_string* ZEND_FASTCALL zend_string_init_interned_request(const char *
297317
return zend_add_interned_string(ret, &CG(interned_strings), 0);
298318
}
299319

320+
static zend_string* ZEND_FASTCALL zend_string_init_existing_interned_request(const char *str, size_t size, bool permanent)
321+
{
322+
zend_ulong h = zend_inline_hash_func(str, size);
323+
zend_string *ret = zend_interned_string_ht_lookup_ex(h, str, size, &interned_strings_permanent);
324+
if (ret) {
325+
return ret;
326+
}
327+
328+
ret = zend_interned_string_ht_lookup_ex(h, str, size, &CG(interned_strings));
329+
if (ret) {
330+
return ret;
331+
}
332+
333+
ZEND_ASSERT(!permanent);
334+
ret = zend_string_init(str, size, permanent);
335+
ZSTR_H(ret) = h;
336+
return ret;
337+
}
338+
300339
ZEND_API void zend_interned_strings_activate(void)
301340
{
302341
zend_init_interned_strings_ht(&CG(interned_strings), 0);
@@ -307,20 +346,23 @@ ZEND_API void zend_interned_strings_deactivate(void)
307346
zend_hash_destroy(&CG(interned_strings));
308347
}
309348

310-
ZEND_API void zend_interned_strings_set_request_storage_handlers(zend_new_interned_string_func_t handler, zend_string_init_interned_func_t init_handler)
349+
ZEND_API void zend_interned_strings_set_request_storage_handlers(zend_new_interned_string_func_t handler, zend_string_init_interned_func_t init_handler, zend_string_init_existing_interned_func_t init_existing_handler)
311350
{
312351
interned_string_request_handler = handler;
313352
interned_string_init_request_handler = init_handler;
353+
interned_string_init_existing_request_handler = init_existing_handler;
314354
}
315355

316356
ZEND_API void zend_interned_strings_switch_storage(bool request)
317357
{
318358
if (request) {
319359
zend_new_interned_string = interned_string_request_handler;
320360
zend_string_init_interned = interned_string_init_request_handler;
361+
zend_string_init_existing_interned = interned_string_init_existing_request_handler;
321362
} else {
322363
zend_new_interned_string = zend_new_interned_string_permanent;
323364
zend_string_init_interned = zend_string_init_interned_permanent;
365+
zend_string_init_existing_interned = zend_string_init_existing_interned_permanent;
324366
}
325367
}
326368

Zend/zend_string.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ BEGIN_EXTERN_C()
2626
typedef void (*zend_string_copy_storage_func_t)(void);
2727
typedef zend_string *(ZEND_FASTCALL *zend_new_interned_string_func_t)(zend_string *str);
2828
typedef zend_string *(ZEND_FASTCALL *zend_string_init_interned_func_t)(const char *str, size_t size, bool permanent);
29+
typedef zend_string *(ZEND_FASTCALL *zend_string_init_existing_interned_func_t)(const char *str, size_t size, bool permanent);
2930

3031
ZEND_API extern zend_new_interned_string_func_t zend_new_interned_string;
3132
ZEND_API extern zend_string_init_interned_func_t zend_string_init_interned;
33+
/* Init an interned string if it already exists, but do not create a new one if it does not. */
34+
ZEND_API extern zend_string_init_existing_interned_func_t zend_string_init_existing_interned;
3235

3336
ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str);
3437
ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len);
@@ -46,7 +49,10 @@ ZEND_API void zend_interned_strings_init(void);
4649
ZEND_API void zend_interned_strings_dtor(void);
4750
ZEND_API void zend_interned_strings_activate(void);
4851
ZEND_API void zend_interned_strings_deactivate(void);
49-
ZEND_API void zend_interned_strings_set_request_storage_handlers(zend_new_interned_string_func_t handler, zend_string_init_interned_func_t init_handler);
52+
ZEND_API void zend_interned_strings_set_request_storage_handlers(
53+
zend_new_interned_string_func_t handler,
54+
zend_string_init_interned_func_t init_handler,
55+
zend_string_init_existing_interned_func_t init_existing_handler);
5056
ZEND_API void zend_interned_strings_switch_storage(bool request);
5157

5258
ZEND_API extern zend_string *zend_empty_string;

ext/opcache/ZendAccelerator.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2878,7 +2878,12 @@ static int zend_accel_init_shm(void)
28782878
*STRTAB_HASH_TO_SLOT(&ZCSG(interned_strings), 0) = STRTAB_INVALID_POS;
28792879
}
28802880

2881-
zend_interned_strings_set_request_storage_handlers(accel_new_interned_string_for_php, accel_init_interned_string_for_php);
2881+
/* We can reuse init_interned_string_for_php for the "init_existing_interned" case,
2882+
* because the function does not create new interned strings at runtime. */
2883+
zend_interned_strings_set_request_storage_handlers(
2884+
accel_new_interned_string_for_php,
2885+
accel_init_interned_string_for_php,
2886+
accel_init_interned_string_for_php);
28822887

28832888
zend_reset_cache_vars();
28842889

@@ -3198,7 +3203,10 @@ static zend_result accel_post_startup(void)
31983203
#endif
31993204
zend_shared_alloc_lock();
32003205
accel_shared_globals = (zend_accel_shared_globals *) ZSMMG(app_shared_globals);
3201-
zend_interned_strings_set_request_storage_handlers(accel_new_interned_string_for_php, accel_init_interned_string_for_php);
3206+
zend_interned_strings_set_request_storage_handlers(
3207+
accel_new_interned_string_for_php,
3208+
accel_init_interned_string_for_php,
3209+
accel_init_interned_string_for_php);
32023210
zend_shared_alloc_unlock();
32033211
break;
32043212
case FAILED_REATTACHED:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Bug #81142 (memory leak when unserialize()ing associative array)
3+
--FILE--
4+
<?php
5+
$mem0 = memory_get_usage();
6+
$ctr = 0;
7+
unserialize(serialize(["foo_$ctr" => 1]));
8+
$mem1 = memory_get_usage();
9+
var_dump($mem1 - $mem0);
10+
?>
11+
--EXPECT--
12+
int(0)

ext/standard/var_unserializer.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ use_double:
10241024
10251025
if (!var_hash) {
10261026
/* Array or object key unserialization */
1027-
ZVAL_STR(rval, zend_string_init_interned(str, len, 0));
1027+
ZVAL_STR(rval, zend_string_init_existing_interned(str, len, 0));
10281028
} else {
10291029
ZVAL_STRINGL_FAST(rval, str, len);
10301030
}

0 commit comments

Comments
 (0)