Skip to content

Assertion `(flag & (1<<3)) == 0' failed. #10251

Closed

Description

@Changochen

Description

The following code:

<?php
class A
{
    function __set($o, $l)
    {
        $this->$p = $v;
    }
}
$a = new A();
$pp = "";
$op = $pp & "";
$a->$op = 0;
?>

Resulted in this output:

/Zend/zend_hash.c:763: zval *_zend_hash_add_or_update_i(HashTable *, zend_string *, zval *, uint32_t): Assertion `(flag & (1<<3)) == 0' failed.

Build config:

./configure --disable-all --enable-address-sanitizer --disable-phpdbg --disable-cgi --with-pic --enable-debug-assertions
make -j

PHP Version

PHP 8.3.0-dev

Operating System

No response

Activity

  1. ndossche commented on Jan 6, 2023

    @ndossche
    Member

    I confirm this happens on 8.1 and higher.

    Analysis:

    Upon attempting to store the empty string in the magic set method we first reach this code

    /* mark pointer as "special" using low bit */
    zend_hash_add_new_ptr(guards, str,
    (void*)(((zend_uintptr_t)&Z_PROPERTY_GUARD_P(zv)) | 1));

    but the strings str and member are actually equal content-wise (their pointers are different, but the characters are the same: both the empty string). This shouldn't happen because of the string equality check above. Later we execute the following:
    return (uint32_t*)zend_hash_add_new_ptr(guards, member, ptr);

    And now a new entry isn't actually added because the first snippet I linked added it. Therefore the assertion fails.

    Cause:

    The following check should've prevented this:

    if (EXPECTED(str == member) ||
    /* "str" always has a pre-calculated hash value here */
    (EXPECTED(ZSTR_H(str) == zend_string_hash_val(member)) &&
    EXPECTED(zend_string_equal_content(str, member)))) {

    But it turns out that the hash of str is zero here! I did a quick test by changing ZSTR_H(str) to zend_string_hash_val(str) and now the OP's code no longer crashes, indicating that the hash being zero is indeed the problem.

  2. Girgias commented on Jan 7, 2023

    @Girgias
    Member

    I would expect that any empty string to be "optimized" to the interned zend_empty_string zend_string which has the hash precomputed. So it seems the is an issue with the bitwise & operator (which might be generalized to other operators)

  3. ndossche commented on Jan 7, 2023

    @ndossche
    Member

    Yeah it also breaks with bitwise |.

  4. nikic commented on Jan 7, 2023

    @nikic
    Member

    The line comparing hashes should just be dropped. member is not guaranteed to have a hash, and member is assigned to str, so it doesn't necessarily have a hash either. (Or else we'd have to force a hash calculation when assigning str.)

  5. ndossche commented on Jan 7, 2023

    @ndossche
    Member

    I see, I'll make a PR for that change this afternoon.

  6. added 2 commits that reference this issue on Jan 7, 2023
    8fc137b
    16a3ba8
  7. added 2 commits that reference this issue on Jan 8, 2023
    d03025b
    e308dc0
  8. added a commit that references this issue on Jan 8, 2023
    3b8327a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions