Skip to content

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

Closed
wants to merge 18 commits into from
Closed

Fix range() segfaults #1695

wants to merge 18 commits into from

Conversation

tpunt
Copy link
Contributor

@tpunt tpunt commented Dec 24, 2015

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:

  • Rename RANGE_CHECK_INIT_ARRAY to RANGE_CHECK_DOUBLE_INIT_ARRAY to better distinguish its purpose from the new RANGE_CHECK_LONG_INIT_ARRAY macro
  • Remove superfluous fabs() calls in RANGE_CHECK_DOUBLE_INIT_ARRAY (and useless ternary check in php_error_docref for the same macro)

tpunt added 16 commits December 15, 2015 23:21
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) { \
Copy link
Member

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 >=?

Copy link
Contributor Author

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) { \
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@smalyshev smalyshev added the Bug label Dec 29, 2015
php-pulls pushed a commit that referenced this pull request Jan 11, 2016
php-pulls pushed a commit that referenced this pull request Jan 11, 2016
* PHP-7.0:
  update NEWS
  patch for github PR #1695
  fix out format
@php-pulls
Copy link

Comment on behalf of ab at php.net:

Merged to target 7.0.3. Thanks!

@php-pulls php-pulls closed this Jan 11, 2016
php-pulls pushed a commit that referenced this pull request Jan 11, 2016
This reverts commit 58dd956.

crashes on travis
php-pulls pushed a commit that referenced this pull request Jan 11, 2016
This reverts commit 58dd956.

crashes on travis
php-pulls pushed a commit that referenced this pull request Jan 11, 2016
* PHP-7.0:
  Revert "update NEWS"
  Revert "patch for github PR #1695"
@weltling
Copy link
Contributor

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.

@tpunt
Copy link
Contributor Author

tpunt commented Jan 11, 2016

Hi!

The problem is that you only partially applied the patch :) The current PR fixes the segfaults occurring in both the else block (to handle longs as inputs), as well as the code in the following block (to handle doubles as inputs):

} else if (Z_TYPE_P(zlow) == IS_DOUBLE || Z_TYPE_P(zhigh) == IS_DOUBLE || is_step_double) {
    // handle input when either min, max, or step is a double
}

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-pulls pushed a commit that referenced this pull request Jan 12, 2016
php-pulls pushed a commit that referenced this pull request Jan 12, 2016
* PHP-7.0:
  re-apply patch for github PR #1695
@weltling
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants