Skip to content

php-memcached corrupts interned strings #338

Closed
@arjenschol

Description

@arjenschol

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)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions