Work around APCu memory corruption bug
authorOri Livneh <ori@wikimedia.org>
Sun, 13 Dec 2015 06:09:32 +0000 (22:09 -0800)
committerOri Livneh <ori@wikimedia.org>
Mon, 14 Dec 2015 05:35:59 +0000 (21:35 -0800)
APCu's object serialization causes memory corruption when apc.serializer is set
to 'default' (see https://github.com/krakjoe/apcu/issues/38). We can work
around this bug by falling back to the pre-I4b2cf17155 behavior of
APCBagOStuff, which is not to trust apc_store() with anything other than
strings and integers, and instead serialize everything in user-space PHP code.

Bug: T120267
Change-Id: If34a1d959e2700792514b372af52919940222d83

includes/libs/objectcache/APCBagOStuff.php

index b5419b7..c7f9c57 100644 (file)
  * @ingroup Cache
  */
 class APCBagOStuff extends BagOStuff {
+
+       /**
+        * @var bool If true, trust the APC implementation to serialize and
+        * deserialize objects correctly. If false, (de-)serialize in PHP.
+        */
+       protected $nativeSerialize;
+
        /**
         * @var string String to append to each APC key. This may be changed
         *  whenever the handling of values is changed, to prevent existing code
         *  from encountering older values which it cannot handle.
-        **/
-       const KEY_SUFFIX = ':1';
+        */
+       const KEY_SUFFIX = ':2';
+
+       /**
+        * Constructor
+        *
+        * Available parameters are:
+        *   - nativeSerialize:     If true, pass objects to apc_store(), and trust it
+        *                          to serialize them correctly. If false, serialize
+        *                          all values in PHP.
+        *
+        * @param array $params
+        */
+       public function __construct( array $params = array() ) {
+               parent::__construct( $params );
+
+               if ( isset( $params['nativeSerialize'] ) ) {
+                       $this->nativeSerialize = $params['nativeSerialize'];
+               } elseif ( extension_loaded( 'apcu' ) && ini_get( 'apc.serializer' ) === 'default' ) {
+                       // APCu has a memory corruption bug when the serializer is set to 'default'.
+                       // See T120267, and upstream bug reports:
+                       //  - https://github.com/krakjoe/apcu/issues/38
+                       //  - https://github.com/krakjoe/apcu/issues/35
+                       //  - https://github.com/krakjoe/apcu/issues/111
+                       $this->logger->warning(
+                               'The APCu extension is loaded and the apc.serializer INI setting ' .
+                               'is set to "default". This can cause memory corruption! ' .
+                               'You should change apc.serializer to "php" instead. ' .
+                               'See <https://github.com/krakjoe/apcu/issues/38>.'
+                       );
+                       $this->nativeSerialize = false;
+               } else {
+                       $this->nativeSerialize = true;
+               }
+       }
 
        protected function doGet( $key, $flags = 0 ) {
                $val = apc_fetch( $key . self::KEY_SUFFIX );
 
+               if ( is_string( $val ) && !$this->nativeSerialize ) {
+                       $val = $this->isInteger( $val )
+                               ? intval( $val )
+                               : unserialize( $val );
+               }
+
                return $val;
        }
 
        public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+               if ( !$this->nativeSerialize && !$this->isInteger( $value ) ) {
+                       $value = serialize( $value );
+               }
+
                apc_store( $key . self::KEY_SUFFIX, $value, $exptime );
 
                return true;