hi everyone,

sorry to drag something from the bug system onto the ML, but i'm unable
to comment on the closed bug.  furthermore, the topic was already brought
up in the "strol for int parsing" thread, so i dare say that it is
relevant :)

afaict the bug in question is closed and marked bogus/duplicate due to
bug #51023.  however, these are two different bugs in two different
paths of code; only the symptoms (and the underlying programming error)
are the same.  i wouldn't be surprised if this problem were found elsewhere
as well...

the problem in both cases is that you can't rely on the wraparound
behavior in signed integer arithmetic.  it's not defined in any C standard
and as mentioned in the previous thread leads to buggy behavior in recent
versions of gcc, since they may be smart enough to optimize out the
conditions that would detect overflow.  an example from the affected
code in this case (gcc-4.4 with -O2):

rangda[~/debian/php] php -r 'var_dump(array("9223372036854775808" => 1));'
array(1) {
  [-9223372036854775808]=>
  int(1)
}

vs. the expected:

rangda[~/debian/php] ./cli-build/sapi/cli/php -r 
'var_dump(array("9223372036854775808" => 1));'
array(1) {
  ["9223372036854775808"]=>
  int(1)
}

(and likewise for the minimum extreme).

The problem can be solved by calculating when an overflow *will* occur,
instead of when it *did* occur.  i.e. instead of

        val = val * 10 + digit;
        if (val < 0) /* overflow */

do:

        if (val <= (LONG_MAX-digit)/10) 
                val = val * 10 + digit;
        else /* overflow */

(and likewise, something similar for underflow detection past LONG_MIN)

so, here's a patch against zend_hash.h which fixes the problem in the
location reported in #51008.  i wasn't able to figure out what
the convention was for tabs before the backslash escape, so i kinda
winged it.  i also took the liberty of improving the test to more fully
illustrate the failures, similar to what i did in the patch for #51023.

i haven't tested this patch extensively but it does pass both the old
and new version of the test.  thoughts and review appreciated.  we also
have the patch in our git repo:

http://git.debian.org/?p=pkg-php/php.git;a=blob;f=debian/patches/zend_int_overflow.patch;h=3df7fa0853af0f04b3427e361a3eccfe66c4c3ae;hb=0fe497525d46b2fa8353e37106b47c05ef804cc5


        sean

--- php.orig/Zend/zend_hash.h
+++ php/Zend/zend_hash.h
@@ -306,9 +306,11 @@ END_EXTERN_C()
 
 #define ZEND_HANDLE_NUMERIC(key, length, func) do {                            
                        \
        register const char *tmp = key;                                         
                                        \
+       int negative = 0;                                                       
                                \
                                                                                
                                                                        \
        if (*tmp == '-') {                                                      
                                                        \
                tmp++;                                                          
                                                                \
+               negative = 1;                                                   
                                                                        \
        }                                                                       
                                                                        \
        if (*tmp >= '0' && *tmp <= '9') { /* possibly a numeric index */        
        \
                const char *end = key + length - 1;                             
                                        \
@@ -322,19 +324,19 @@ END_EXTERN_C()
                     *tmp > '2')) { /* overflow */                              
                                        \
                        break;                                                  
                                                                \
                }                                                               
                                                                        \
-               idx = (*tmp - '0');                                             
                                                        \
+               idx = ((negative)?-1:1) * (*tmp - '0');                         
                                                                        \
                while (++tmp != end && *tmp >= '0' && *tmp <= '9') {            
                \
-                       idx = (idx * 10) + (*tmp - '0');                        
                                        \
+                       int digit = (*tmp - '0');                               
                                \
+                       if ( (!negative) && idx <= (LONG_MAX-digit)/10 ) {      
                                \
+                               idx = (idx * 10) + digit;                       
                                        \
+                       } else if ( (negative) && idx >= (LONG_MIN+digit)/10 ) 
{                                \
+                               idx = (idx * 10) - digit;                       
                                        \
+                       } else {                                                
                                                                                
\
+                               --tmp; /* overflow or underflow, make sure tmp 
!= end */                        \
+                               break;                                          
                                                                                
\
+                       }                                                       
                                                                        \
                }                                                               
                                                                        \
                if (tmp == end) {                                               
                                                        \
-                       if (*key == '-') {                                      
                                                        \
-                               idx = -idx;                                     
                                                                \
-                               if (idx > 0) { /* overflow */                   
                                        \
-                                       break;                                  
                                                                \
-                               }                                               
                                                                        \
-                       } else if (idx < 0) { /* overflow */                    
                                \
-                               break;                                          
                                                                \
-                       }                                                       
                                                                        \
                        return func;                                            
                                                        \
                }                                                               
                                                                        \
        }                                                                       
                                                                        \
--- php.orig/Zend/tests/bug45877.phpt
+++ php/Zend/tests/bug45877.phpt
@@ -1,23 +1,40 @@
 --TEST--
 Bug #45877 (Array key '2147483647' left as string)
---INI--
-precision=20
 --FILE--
 <?php
-$keys = array(PHP_INT_MAX,
-       (string) PHP_INT_MAX,
-       (string) (-PHP_INT_MAX - 1),
-       -PHP_INT_MAX - 1,
-       (string) (PHP_INT_MAX + 1));
+$max = sprintf("%d", PHP_INT_MAX);
+switch($max) {
+case "2147483647": /* 32-bit systems */
+       $min = "-2147483648";
+       $overflow = "2147483648";
+       $underflow = "-2147483649";
+       break;
+case "9223372036854775807": /* 64-bit systems */
+       $min = "-9223372036854775808";
+       $overflow = "9223372036854775808";
+       $underflow = "-9223372036854775809";
+       break;
+default:
+       die("failed: unknown value for PHP_MAX_INT");
+       break;
+}
 
-var_dump(array_fill_keys($keys, 1));
-?>
---EXPECTF--
-array(3) {
-  [%d7]=>
-  int(1)
-  [-%d8]=>
-  int(1)
-  ["%d8"]=>
-  int(1)
+function test_value($val, $msg) {
+       $a = array($val => 1);
+       $keys = array_keys($a);
+       if ($val == $keys[0]) $result = "ok";
+       else $result = "failed ($val != $keys[0])";
+       echo "$msg: $result\n";
 }
+
+test_value($max, "max");
+test_value($overflow, "overflow");
+test_value($min, "min");
+test_value($underflow, "underflow");
+
+?>
+--EXPECT--
+max: ok
+overflow: ok
+min: ok
+underflow: ok

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to