ext/date: guard property-table reads in php_date_interval_initialize_from_hash()#22822
Open
brzuchal wants to merge 1 commit into
Open
ext/date: guard property-table reads in php_date_interval_initialize_from_hash()#22822brzuchal wants to merge 1 commit into
brzuchal wants to merge 1 commit into
Conversation
…from_hash()
php_date_interval_initialize_from_hash() reads the interval fields out of a
HashTable which, for an uninitialized DateInterval, is the object's raw
property table: date_object_get_properties_interval() returns
zend_std_get_properties() unchanged when !intervalobj->initialized.
A raw property table stores declared properties as IS_INDIRECT slots. The
PHP_DATE_INTERVAL_READ_PROPERTY() family already accounts for this with a
Z_TYPE_P(z_arg) <= IS_STRING guard, so the eighteen fields read through it
are safe. The two fields read open-coded, "f" and "civil_or_wall", have no
such guard and pass the IS_INDIRECT zval straight to zval_get_double() and
zval_get_long(), which do not accept it and fall into ZEND_UNREACHABLE().
class B extends DateInterval { public float $f = 1.5; }
$o = (new ReflectionClass('B'))->newInstanceWithoutConstructor();
$o->__wakeup();
In a debug build this aborts on an assertion in zval_get_double_func(). In a
release build it is undefined behaviour; as observed the declared value is
silently discarded rather than diagnosed.
ext/zlib solves the same class of problem at the call site with
ZVAL_DEINDIRECT(), whose comment in zlib.c notes that the |H ZPP specifier
may leave HashTable entries wrapped in IS_INDIRECT.
Apply the guard the sibling reads already use, and add a regression test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
php_date_interval_initialize_from_hash()reads the interval fields out of aHashTablethat, for an uninitialized
DateInterval, is the object's raw property table:date_object_get_properties_interval()returnszend_std_get_properties()unchanged when!intervalobj->initialized.A raw property table stores declared properties as
IS_INDIRECTslots. Two of the fields areread open-coded without a type guard and hand that zval straight to
zval_get_double()/zval_get_long(), which have nocase IS_INDIRECT:and fall intoZEND_UNREACHABLE().Reproducer
Debug build:
Release build: no diagnostic at all — the declared value is silently discarded.
newInstanceWithoutConstructor()is what leavesinitializedfalse.unserialize()is not aroute here, because
DateIntervaldefines__unserialize()and that takes precedence over__wakeup().Fix
The
PHP_DATE_INTERVAL_READ_PROPERTY()family already accounts for this — all three variants(plain,
_I64,_DAYS) guard withZ_TYPE_P(z_arg) <= IS_STRING, which excludesIS_INDIRECT(12). Eighteen fields are read through them and are safe. Only"f"and"civil_or_wall"are read open-coded, and they lack the guard; this applies the same guard toboth.
ext/zlibsolves the same class of problem at the call site withZVAL_DEINDIRECT(); thecomment there notes that the
|HZPP specifier may leaveHashTableentries wrapped inIS_INDIRECT.Behaviour
On an uninitialized instance, a
DateIntervalsubclass declaring$for$civil_or_wallnowhas those fields ignored deterministically — exactly as the eighteen sibling fields already
are — instead of hitting undefined behaviour. Nothing else changes: a plain
DateInterval,dynamic properties, and the normal serialize/unserialize round-trip are unaffected.
Tests
New
ext/date/tests/date_interval_wakeup_declared_property.phptcovers thedeclared-initialized, declared-uninitialized and
civil_or_wallcases, plus adynamic-property control (still read correctly) and a serialize/unserialize round-trip.
ext/date/tests: 680/680 pass, debug build.