Description
Crosspost from https://bugs.php.net/bug.php?id=74297
The comparison is just a symptom caused by memcached clearing memory in some wrong way.
This might even by somewhat of a security problem: If you know user input is cached, an attacker can choose to use strings which are also likely to be used in other code (i.e. targeted attack or popular frameworks) and trigger the unexpected null values somewhere down the line.
When the interned string cache buffer is configured to 0, everything works fine: php -n -dextension=memcached.so -dzend_extension=opcache.so -dopcache.interned_strings_buffer=0 test.php
Description:
php-memcached 3.0.3
libmemcached 1.0.18
memcached 1.4.32
php 7.1.4
Both memcached and opcache needs to be loaded. opcache.enable_cli is not needed.
php -n -dextension=memcached.so -dzend_extension=opcache.so script.php
If the value ('test') set in memcached is changed, the script no longer fails.
($e == $a) === false, while ($a == $e) === true...
Test script:
<?php
class r
{
public $d = ['test' => 'x'];
public function m()
{
return [] + $this->d;
}
}
(function()
{
$m = new memcached;
$m->addServer('127.0.0.1', 11211);
$m->set('Something', 'test');
$e = ['test' => 'x'];
$a = (new r)->m();
if (0 == ($e <=> $a))
return 'success';
var_dump([$e, $a]);
var_dump([serialize($e), serialize($a)]);
var_dump([
'e === a' => $e === $a,
'e == a' => $e == $a,
'e <=> a' => $e <=> $a,
'a <=> e' => $a <=> $e,
'a === e' => $a === $e,
'a == e' => $a == $e]
);
var_dump(debug_zval_dump($e), debug_zval_dump($a));
die('this should not happen');
})();
Expected result:
success
Actual result:
array(2) {
[0]=>
array(1) {
["test"]=>
string(1) "x"
}
[1]=>
array(1) {
["test"]=>
string(1) "x"
}
}
array(2) {
[0]=>
string(25) "a:1:{s:4:"test";s:1:"x";}"
[1]=>
string(25) "a:1:{s:4:"test";s:1:"x";}"
}
array(6) {
["e === a"]=>
bool(true)
["e == a"]=>
bool(false)
["e <=> a"]=>
int(1)
["a <=> e"]=>
int(0)
["a === e"]=>
bool(true)
["a == e"]=>
bool(true)
}
array(1) refcount(1){
["test"]=>
string(1) "x" refcount(1)
}
array(1) refcount(2){
["test"]=>
string(1) "x" refcount(1)
}