Problem:

The (int) conversion in _db_query_callback() causes all integers that are bigger than the maximum value of a signed integer to overflow.

This can be a problem when working with dates >= ~year 2038. The data type for timestamps used in Drupal is UNSIGNED INT(11) with a range between 0- 4294967295. The (int) conversion of %d modifier variables in _db_query_callback() forces to a range of -2147483648 to 2147483647. (Is this range platform dependent?)

Demonstration:

$maxdateunsigned = 4294967295; // 2106-02-07 02:28:15 - already 2147483648 would cause an overflow
$maxdatesigned = 2147483647; // 2038-01-18 23:14:07

db_query("INSERT INTO {node} ( `nid` , 
                               `vid` , 
                               `type` , 
                               `title` , 
                               `uid` , 
                               `status` , 
                               `created` , 
                               `changed` , 
                               `comment` , 
                               `promote` , 
                               `moderate` , 
                               `sticky` )
          VALUES (
          %d , %d, '', '', '0', '1', %d, '0', '0', '0', '0', '0'
          )", $maxdatesigned, $maxdatesigned, $maxdatesigned);

/*
resulting query: 
INSERT INTO node ( `nid` , `vid` , `type` , `title` , `uid` , `status` , `created` , `changed` , `comment` , `promote` , `moderate` , `sticky` ) VALUES ( 2147483647 , 2147483647, '', '', '0', '1', 2147483647, '0', '0', '0', '0', '0' )
*/

db_query("INSERT INTO {node} ( `nid` , 
                               `vid` , 
                               `type` , 
                               `title` , 
                               `uid` , 
                               `status` , 
                               `created` , 
                               `changed` , 
                               `comment` , 
                               `promote` , 
                               `moderate` , 
                               `sticky` )
          VALUES (
          %d , %d, '', '', '0', '1', %d, '0', '0', '0', '0', '0'
          )", $maxdateunsigned, $maxdateunsigned, $maxdateunsigned);

/*
resulting query: 
INSERT INTO node ( `nid` , `vid` , `type` , `title` , `uid` , `status` , `created` , `changed` , `comment` , `promote` , `moderate` , `sticky` ) VALUES ( -1 , -1, '', '', '0', '1', -1, '0', '0', '0', '0', '0' )
*/

Solution:

Passing the value through modifier %f circumvents this problem. The actual problem is that PHP is lacking an (unsigned int) type conversion. An intermediate solution might be to implement a fake %u modifier for unsigned integers that converts with (float):

function _db_query_callback($match, $init = FALSE) {
  static $args = NULL;
  if ($init) {
    $args = $match;
    return;
  }

  switch ($match[1]) {
    case '%d': // We must use type casting to int to convert FALSE/NULL/(TRUE?)
      return (int) array_shift($args); // We don't need db_escape_string as numbers are db-safe
    // new
    case '%u': 
      return (float) array_shift($args);
    case '%s':
      return db_escape_string(array_shift($args));
    case '%%':
      return '%';
    case '%f':
      return (float) array_shift($args);
    case '%b': // binary data
      return db_encode_blob(array_shift($args));
  }
}

Comments

chx’s picture

Status: Active » Closed (duplicate)