-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix range() segfaults #1695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix range() segfaults #1695
Conversation
Both segfaults occur due to accuracy lost when representing 64bit longs as doubles. The segfaults can be replicated with: range(PHP_INT_MIN + 513, PHP_INT_MIN); // Seg fault on line 2236 range(PHP_INT_MAX - 512, PHP_INT_MAX); // Seg fault on line 2249
This caused a few tests to fail because a different error message was being output
By using the calculated array size to determine the number of loop iterations, we no longer need to perform overflow checks in the loops
This is more consistent with the naming used by the RANGE_CHECK_LONG_INIT_ARRAY macro, and keeps the purpose of the two better distinguished
'start' is always be greater then 'end' - if it wasn't, '__calc_size' would be incorrect (it would be 2 less than the actual size, due to the +1 at the end)
|
||
#define RANGE_CHECK_LONG_INIT_ARRAY(start, end) do { \ | ||
double __calc_size = (start - end) / lstep; \ | ||
if (__calc_size >= HT_MAX_SIZE) { \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is double used here for __calc_size
? Also, should this compare against HT_MAX_SIZE-1
as we're adding one below? Or is that -1 implicit in the >=
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, __calc_size
should be zend_ulong here. I think the check in the RANGE_CHECK_LONG_INIT_ARRAY
is correct, and the one in RANGE_CHECK_DOUBLE_INIT_ARRAY
is slightly off (it should only compare if __calc_size > (double)HT_MAX_SIZE
, since 1 has already been added to account for the inclusive range).
double __calc_size = ((start - end) / step) + 1; \ | ||
if (fabs(__calc_size) >= (double)HT_MAX_SIZE) { \ | ||
php_error_docref(NULL, E_WARNING, "The supplied range exceeds the maximum array size: start=%0.0f end=%0.0f", start > end ? end : start, start > end ? start : end); \ | ||
if (__calc_size > (double)HT_MAX_SIZE) { \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous condition was correct. fabs() is needed for the case start < end, the >= descends from http://lxr.php.net/xref/PHP_7_0/Zend/zend_hash.c#191
Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
start < end
never occurs, and if it did, the size calculation would be incorrect. This is because ((start - end) / step)
(= n
) would yield a negative, then + 1
and take the absolute value of that would make the size n - 1
, rather than n + 1
(for an inclusive range). If we look at where the macro is invoked, we can see that these checks (to ensure that start > end
) are already performed:
http://lxr.php.net/xref/PHP_7_0/ext/standard/array.c#2185 (the enclosing if
ensures low > high)
http://lxr.php.net/xref/PHP_7_0/ext/standard/array.c#2198 (the enclosing else if
ensures high > low)
Regarding the size check against HT_MAX_SIZE
, can the maximum size of an array only be HT_MAX_SIZE - 1
then (and not HT_MAX_SIZE
)? If so, I'll update the PR to reflect that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, with argument swapping it will work as well, that's correct then.
With HT_MAX_SIZE - yes, like in the place i've linked earlier, or fe here http://lxr.php.net/xref/PHP_7_0/Zend/zend_hash.c#zend_hash_check_size . Or you can check other usages. Size has to be always < HT_MAX_SIZE, thus the previuos >=.
Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I've corrected the condition to ensure __calc_size
is less than HT_MAX_SIZE
with 55f6132.
Thanks.
* PHP-7.0: update NEWS patch for github PR #1695 fix out format
Comment on behalf of ab at php.net: Merged to target 7.0.3. Thanks! |
This reverts commit 58dd956. crashes on travis
This reverts commit 58dd956. crashes on travis
* PHP-7.0: Revert "update NEWS" Revert "patch for github PR #1695"
I've reverted these because travis was showing segfaults on this exact patch. Please see https://travis-ci.org/php/php-src/jobs/101677405 . Have to investigate, what went wrong there. Please take a look as well. Thanks. |
Hi! The problem is that you only partially applied the patch :) The current PR fixes the segfaults occurring in both the
This is where the current segfaults are occurring in test range_bug71132.phpt - please apply this part of the patch too and it should fix it :) |
* PHP-7.0: re-apply patch for github PR #1695
Yeah, that was the issue. On my side it was just that git am was breaking in the middle, so i've decided to simply fetch the patch. But github seems to make difference when requesting .patch vs .diff. Reapllied the .diff now and everything seems fine. Thanks. |
The following PR fixes bug #71132 and bug #71197. See PR #1677 and PR #1690, respectively, for more information on them.
I've also tidied up range() a little bit:
RANGE_CHECK_INIT_ARRAY
toRANGE_CHECK_DOUBLE_INIT_ARRAY
to better distinguish its purpose from the newRANGE_CHECK_LONG_INIT_ARRAY
macroRANGE_CHECK_DOUBLE_INIT_ARRAY
(and useless ternary check inphp_error_docref
for the same macro)