php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81142 PHP 7.3+ memory leak when unserialize() is used on an associative array
Submitted: 2021-06-15 17:27 UTC Modified: 2021-09-14 16:16 UTC
Votes:4
Avg. Score:3.5 ± 1.7
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: jnelson at archive dot org Assigned:
Status: Closed Package: Arrays related
PHP Version: 7.3.28 OS:
Private report: No CVE-ID: None
 [2021-06-15 17:27 UTC] jnelson at archive dot org
Description:
------------
As seen here: https://3v4l.org/KPJ62#focus=eol

This program loops thousands of times calling serialize() and unserialize() on associative arrays with a string key and an integer value without holding long-term references to any of the data.

Prior to 7.3, this program would peak at a few hundred KB.  With 7.3.0 forward, this program consumes megabytes.

The two flags at the top of the program can be used to confirm (a) the memory leak only occurs with associative arrays, and (b) it's caused by unserialize() and not serialize().

Test script:
---------------
The core of the linked program to repro the problem:

function load($str) {
    $php = serialize([ $str => 1 ]);
    unserialize($php); 
}

for ($ctr = 0; $ctr < 50000; $ctr++)
    load("foo_$ctr");

echo memory_get_peak_usage();

Expected result:
----------------
Prior to 7.3.0, my linked program produces this output (or similar numbers):

Usage: 377.99 K
Peak:  414.33 K

Actual result:
--------------
With 7.3.0 to 8.0.3, the numbers look like this:

Usage: 4.73 M
Peak:  5.34 M

Patches

Add a Patch

Pull Requests

Pull requests:

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-06-16 12:11 UTC] cmb@php.net
First, I suggest to use something like

    <?php
    $mem0 = memory_get_usage();
    for ($ctr = 0; $ctr < 50000; $ctr++)
        load("foo_$ctr");
    $mem1 = memory_get_usage();
    var_dump($mem1 - $mem0);
    ?>

to see the *relative* memory usage.

Anyhow, as of PHP 7.3.0, unserialized string keys are interned[1].
If OPcache is enabled, there is actually only one copy for all
50.000 keys.  If OPcache is disabled, these strings are allocated
permanently in process memory.

[1] <https://github.com/php/php-src/commit/03da5f8e30cc65584d38d478ea566062ff99579b>
 [2021-06-16 13:40 UTC] cmb@php.net
-Assigned To: +Assigned To: cmb
 [2021-06-16 13:40 UTC] cmb@php.net
-Status: Assigned +Status: Open
 [2021-06-16 14:31 UTC] cmb@php.net
The following pull request has been associated:

Patch Name: Fix #81142: memory leak when unserialize()ing associative array
On GitHub:  https://github.com/php/php-src/pull/7160
Patch:      https://github.com/php/php-src/pull/7160.patch
 [2021-06-16 14:32 UTC] cmb@php.net
Note that PHP-7.3 is no longer actively supported, so a patch can
target PHP-7.4 at most.
 [2021-07-15 13:59 UTC] cmb@php.net
-Assigned To: cmb +Assigned To:
 [2021-08-12 09:58 UTC] git@php.net
Automatic comment on behalf of nikic
Revision: https://github.com/php/php-src/commit/4a4ae45a0bcb82b01a4386433b2e4ee45862fc01
Log: Fix bug #81142 by adding zend_string_init_existing_interned()
 [2021-08-12 09:58 UTC] git@php.net
-Status: Open +Status: Closed
 [2021-09-13 18:47 UTC] amfriedman at gmail dot com
FYI -- I also reproduced this memory leak on PHP 7.2. Change the bug title?
 [2021-09-14 16:16 UTC] jnelson at archive dot org
The link I included in the initial report tests all versions of PHP from 5.4.0 to 8.0.7, and the leak is only seen in 7.3.0 and above.  Perhaps you're seeing a different type of leak?
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Sun Nov 28 10:03:16 2021 UTC