From:             vesselin dot atanasov at gmail dot com
Operating system: Any
PHP version:      5.4Git-2012-09-28 (Git)
Package:          Scripting Engine problem
Bug Type:         Bug
Bug description:Corruption of hash tables (bugfix attached)

Description:
------------
PHP 5.4 has a bug in the handling if interned (literal) strings.

Each time an HTTP request terminates, the interned strings are restored to
the snapshot that was taken at the end of the startup sequence, which
effectively removes any interned strings that have been added after the
snapshot has been made.

Usually when a hash item is added, the code in zend_hash.c allocates extra
space after the end of the Bucket structure, copies the key there and then
points Bucket::arKey to the key copy. However when dealing with interned
hash keys the code tries to optimize the algorithm by not allocating extra
space after the end of the Bucket structure, but just pointing
Bucket::arKey to the passed arKey parameter.

        if (IS_INTERNED(arKey)) {
                p = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent);
                if (!p) {
                        return FAILURE;
                }
                p->arKey = arKey;
        } else {
                p = (Bucket *) pemalloc(sizeof(Bucket) + nKeyLength, 
ht->persistent);
                if (!p) {
                        return FAILURE;
                }
                p->arKey = (const char*)(p + 1);
                memcpy((char*)p->arKey, arKey, nKeyLength);
        }

The problem happens when a persistent hash table gets an interned key as a
parameter. What happens is that the table and its bucket are persistent,
i.e.
they remain even after the current HTTP request has been terminated, but
the bucket key is removed from the interned keys table and its memory will
be
reused by other interned keys upon the next request. This leads to
corruption of the array keys in the persistent hash table.

One such case is with the PCRE cache. It is initialized in:

php_pcre.c:PHP_GINIT_FUNCTION(pcre)

by the following code:

zend_hash_init(&pcre_globals->pcre_cache, 0, NULL, php_free_pcre_cache,
1);

The last parameter (1) means that it is a persistent hash table. However
the code in
php_pcre.c:pcre_get_compiled_regex_cache(char *regex, int regex_len
TSRMLS_DC)
just passes the regex parameter to zend_hash_update:

zend_hash_update(&PCRE_G(pcre_cache), regex, regex_len+1, (void
*)&new_entry,
                                        sizeof(pcre_cache_entry), (void**)&pce);

Given that in most cases the regex parameter is created from string
literals in the compiled code, this means that in most cases we end up
with
interned, non-persistent keys in the persistent PCRE cache table. So when
the next HTTP request comes and we create different interned strings
they will overwrite the previous ones in the PCRE cache table.

The suggested solution to this bug is to modify the code in zend_hash.c and
change it in such a way that copying of keys is skipped only when the
key is interned and the hash table is persistent. When either the key is
non-interned or the hash table is persistent, the key is copied after the
Bucket structure, so that its maximum lifetime will be the same as the
lifetime of the hash table.

I am attaching a patch which uses the suggested solution. I tested this
patch and it solves the problems with PCRE cache corruption that we
observed in our PHP code. The patch is for PHP 5.4.5 but it also applies
cleanly to the latest git version of PHP 5.4


-- 
Edit bug report at https://bugs.php.net/bug.php?id=63180&edit=1
-- 
Try a snapshot (PHP 5.4):   
https://bugs.php.net/fix.php?id=63180&r=trysnapshot54
Try a snapshot (PHP 5.3):   
https://bugs.php.net/fix.php?id=63180&r=trysnapshot53
Try a snapshot (trunk):     
https://bugs.php.net/fix.php?id=63180&r=trysnapshottrunk
Fixed in SVN:               https://bugs.php.net/fix.php?id=63180&r=fixed
Fixed in release:           https://bugs.php.net/fix.php?id=63180&r=alreadyfixed
Need backtrace:             https://bugs.php.net/fix.php?id=63180&r=needtrace
Need Reproduce Script:      https://bugs.php.net/fix.php?id=63180&r=needscript
Try newer version:          https://bugs.php.net/fix.php?id=63180&r=oldversion
Not developer issue:        https://bugs.php.net/fix.php?id=63180&r=support
Expected behavior:          https://bugs.php.net/fix.php?id=63180&r=notwrong
Not enough info:            
https://bugs.php.net/fix.php?id=63180&r=notenoughinfo
Submitted twice:            
https://bugs.php.net/fix.php?id=63180&r=submittedtwice
register_globals:           https://bugs.php.net/fix.php?id=63180&r=globals
PHP 4 support discontinued: https://bugs.php.net/fix.php?id=63180&r=php4
Daylight Savings:           https://bugs.php.net/fix.php?id=63180&r=dst
IIS Stability:              https://bugs.php.net/fix.php?id=63180&r=isapi
Install GNU Sed:            https://bugs.php.net/fix.php?id=63180&r=gnused
Floating point limitations: https://bugs.php.net/fix.php?id=63180&r=float
No Zend Extensions:         https://bugs.php.net/fix.php?id=63180&r=nozend
MySQL Configuration Error:  https://bugs.php.net/fix.php?id=63180&r=mysqlcfg

Reply via email to