简要概述
这是PHP扩展代码中的一个错误...
我深入研究了包装libmemcached和libmemcached API代码的PHP扩展代码,但我认为已经找到了您问题的可能根本原因...
如果您查看PHP Memcached::increment()
实现,您将在php_memcached.c
的第1858行上看到
status = memcached_increment_with_initial(m_obj->memc, key, key_len, (unsigned int)offset, initial, expiry, &value);
问题在于
offset
的宽度可能是64位或不是。根据
libmemcached API,
memcached_increment_with_initial
函数需要一个
uint64_t
类型的
offset
参数,然而这里的
offset
被声明为
long
类型并转换为
unsigned int
类型。
因此,如果我们像下面这样做...
$memcached = new memcached;
$memcached->addServer('127.0.0.1','11211');
$memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, TRUE);
$memcached->delete('foo');
$memcached->increment('foo',1,1);
var_dump($memcached->get('foo'));
你会看到类似于...
string(22) "8589934592
"
作为该脚本的输出结果。
请注意,这仅在该memcached服务器上不存在关键字foo
的情况下才有效。 还要注意该字符串的长度为
22
个字符,但显然不应该是这样。
如果查看该字符串的十六进制表示....
var_dump(bin2hex($memcached->get('foo')));
结果最终成为一堆毫无意义的垃圾...
string(44) "38353839393334353932000d0a000000000000000000"
被存储的对象在强制类型转换之间明显已经损坏。因此,您可能会得到与我相同的结果,或者像上面演示的那样获得完全破损的数据。这取决于强制类型转换对正在存储的内存块产生的影响(这里跌入了未定义行为)。另外,唯一看起来的根本原因是在初始值中使用增量(随后使用“increment”并不会显示出该问题,或者如果键已经存在)。
我认为这个问题源于libmemcached API对于memcached_increment和memcached_increment_with_initial函数中的“offset”参数有两个不同的大小要求。
memcached_increment(memcached_st *ptr, const char *key, size_t key_length, uint32_t offset, uint64_t *value)
前者使用 uint32_t
,而后者使用 uint64_t
,PHP 的扩展代码将两者都转换为 unsigned int
,这几乎相当于 uint32_t
。
这种 offset
参数宽度上的差异可能导致在调用 PHP 扩展代码和 API 代码之间某种方式损坏密钥。