Hi,
As Maxim said, it's all about integer overflow. Yes, in php, resource type is actually of type long (signed). Below is detailed explanation with code samples (taken from the source);
(for simplicity I will replace the code we're not interested in with ...)
Code: Select all
#define IS_RESOURCE 7
typedef struct _zval_struct zval;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
...
zend_uchar type; /* active type */
...
};
typedef union _zvalue_value {
long lval; /* long value */
...
} zvalue_value;
zval is the basic of all data types in php (long, double, string, resource, object). So all we need to know is that _zval_struct.value is actually zvalue_value structure, which has lval member of type long (signed). Now let's move forward;
Now, mysql_connect actually makes a call to php_mysql_do_connect, so let's just there.
Code: Select all
link = (long) index_ptr->ptr;
ptr = zend_list_find(link,&type); /* check if the link is still there */
if (ptr && (type==le_link || type==le_plink)) {
zend_list_addref(link);
Z_LVAL_P(return_value) = link;
php_mysql_set_default_link(link TSRMLS_CC);
Z_TYPE_P(return_value) = IS_RESOURCE;
efree(hashed_details);
MYSQL_DO_CONNECT_CLEANUP();
return;
}
...
ZEND_REGISTER_RESOURCE(return_value, mysql, le_link);
So here we have our "link" resource casted to (long), then we set the value and the type and register the resource. ZEND_REGISTER_RESOURCE is macros so below is the actual implementation:
Code: Select all
#define ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type) \
zend_register_resource(rsrc_result, rsrc_pointer, rsrc_type);
ZEND_API int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type)
{
int rsrc_id;
rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type);
if (rsrc_result) {
rsrc_result->value.lval = rsrc_id;
rsrc_result->type = IS_RESOURCE;
}
return rsrc_id;
}
ZEND_API int zend_list_insert(void *ptr, int type)
{
int index;
zend_rsrc_list_entry le;
TSRMLS_FETCH();
le.ptr=ptr;
le.type=type;
le.refcount=1;
index = zend_hash_next_free_element(&EG(regular_list));
zend_hash_index_update(&EG(regular_list), index, (void *) &le, sizeof(zend_rsrc_list_entry), NULL);
return index;
}
Now we have inserted the resource and we have its index (which is signed int). So far we've covered the way zend is creative resource links, now let's see how he validates and retrieves them.
Code: Select all
PHP_FUNCTION(mysql_num_rows)
{
zval *result;
MYSQL_RES *mysql_result;
...
ZEND_FETCH_RESOURCE(mysql_result, MYSQL_RES *, &result, -1, "MySQL result", le_result);
/* conversion from int64 to long happing here */
Z_LVAL_P(return_value) = (long) mysql_num_rows(mysql_result);
Z_TYPE_P(return_value) = IS_LONG;
}
Note, ZEND_FETCH_RESOURCE is macros, below is the implementation:
Code: Select all
ZEND_API void *zend_fetch_resource(zval **passed_id TSRMLS_DC, int default_id, char *resource_type_name, int *found_resource_type, int num_resource_types, ...)
{
int id;
int actual_resource_type;
void *resource;
va_list resource_types;
int i;
char *space;
char *class_name;
if (default_id==-1) { /* use id */
... /* id validation done here */
id = (*passed_id)->value.lval;
} else {
id = default_id;
}
resource = zend_list_find(id, &actual_resource_type);
if (!resource) {
if (resource_type_name) {
class_name = get_active_class_name(&space TSRMLS_CC);
zend_error(E_WARNING, "%s%s%s(): %d is not a valid %s resource", class_name, space, get_active_function_name(TSRMLS_C), id, resource_type_name);
}
return NULL;
}
...
}
Ok, its clear what happens here, zend_list_find is not able to find the id in the list;
Code: Select all
ZEND_API void *_zend_list_find(int id, int *type TSRMLS_DC)
{
zend_rsrc_list_entry *le;
if (zend_hash_index_find(&EG(regular_list), id, (void **) &le)==SUCCESS) {
*type = le->type;
return le->ptr;
} else {
*type = -1;
return NULL;
}
}
This is what is happening behind the doors. And now little hint about integer overflow. long (signed) can be between LONG_MIN and LONG_MAX, which on 32bit OS has values in range from -2147483647 to 2147483647. (You can try a little experiment, run this with php: var_dump(-2147483680) and see the result)
Sorry for the long post and hope this helps, you need to rethink the way you are doing this. Good luck.
