Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/libs/objectcache/RedisBagOStuff.php (10 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Object caching using Redis (http://redis.io/).
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
23
/**
24
 * Redis-based caching module for redis server >= 2.6.12
25
 *
26
 * @note: avoid use of Redis::MULTI transactions for twemproxy support
27
 */
28
class RedisBagOStuff extends BagOStuff {
29
	/** @var RedisConnectionPool */
30
	protected $redisPool;
31
	/** @var array List of server names */
32
	protected $servers;
33
	/** @var array Map of (tag => server name) */
34
	protected $serverTagMap;
35
	/** @var bool */
36
	protected $automaticFailover;
37
38
	/**
39
	 * Construct a RedisBagOStuff object. Parameters are:
40
	 *
41
	 *   - servers: An array of server names. A server name may be a hostname,
42
	 *     a hostname/port combination or the absolute path of a UNIX socket.
43
	 *     If a hostname is specified but no port, the standard port number
44
	 *     6379 will be used. Arrays keys can be used to specify the tag to
45
	 *     hash on in place of the host/port. Required.
46
	 *
47
	 *   - connectTimeout: The timeout for new connections, in seconds. Optional,
48
	 *     default is 1 second.
49
	 *
50
	 *   - persistent: Set this to true to allow connections to persist across
51
	 *     multiple web requests. False by default.
52
	 *
53
	 *   - password: The authentication password, will be sent to Redis in
54
	 *     clear text. Optional, if it is unspecified, no AUTH command will be
55
	 *     sent.
56
	 *
57
	 *   - automaticFailover: If this is false, then each key will be mapped to
58
	 *     a single server, and if that server is down, any requests for that key
59
	 *     will fail. If this is true, a connection failure will cause the client
60
	 *     to immediately try the next server in the list (as determined by a
61
	 *     consistent hashing algorithm). True by default. This has the
62
	 *     potential to create consistency issues if a server is slow enough to
63
	 *     flap, for example if it is in swap death.
64
	 * @param array $params
65
	 */
66
	function __construct( $params ) {
67
		parent::__construct( $params );
68
		$redisConf = [ 'serializer' => 'none' ]; // manage that in this class
69
		foreach ( [ 'connectTimeout', 'persistent', 'password' ] as $opt ) {
70
			if ( isset( $params[$opt] ) ) {
71
				$redisConf[$opt] = $params[$opt];
72
			}
73
		}
74
		$this->redisPool = RedisConnectionPool::singleton( $redisConf );
75
76
		$this->servers = $params['servers'];
77
		foreach ( $this->servers as $key => $server ) {
78
			$this->serverTagMap[is_int( $key ) ? $server : $key] = $server;
79
		}
80
81
		if ( isset( $params['automaticFailover'] ) ) {
82
			$this->automaticFailover = $params['automaticFailover'];
83
		} else {
84
			$this->automaticFailover = true;
85
		}
86
87
		$this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_NONE;
88
	}
89
90 View Code Duplication
	protected function doGet( $key, $flags = 0 ) {
91
		list( $server, $conn ) = $this->getConnection( $key );
92
		if ( !$conn ) {
93
			return false;
94
		}
95
		try {
96
			$value = $conn->get( $key );
97
			$result = $this->unserialize( $value );
98
		} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
99
			$result = false;
100
			$this->handleException( $conn, $e );
101
		}
102
103
		$this->logRequest( 'get', $key, $server, $result );
104
		return $result;
105
	}
106
107 View Code Duplication
	public function set( $key, $value, $expiry = 0, $flags = 0 ) {
108
		list( $server, $conn ) = $this->getConnection( $key );
109
		if ( !$conn ) {
110
			return false;
111
		}
112
		$expiry = $this->convertToRelative( $expiry );
113
		try {
114
			if ( $expiry ) {
115
				$result = $conn->setex( $key, $expiry, $this->serialize( $value ) );
116
			} else {
117
				// No expiry, that is very different from zero expiry in Redis
118
				$result = $conn->set( $key, $this->serialize( $value ) );
119
			}
120
		} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
121
			$result = false;
122
			$this->handleException( $conn, $e );
123
		}
124
125
		$this->logRequest( 'set', $key, $server, $result );
126
		return $result;
127
	}
128
129
	public function delete( $key ) {
130
		list( $server, $conn ) = $this->getConnection( $key );
131
		if ( !$conn ) {
132
			return false;
133
		}
134
		try {
135
			$conn->delete( $key );
136
			// Return true even if the key didn't exist
137
			$result = true;
138
		} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
139
			$result = false;
140
			$this->handleException( $conn, $e );
141
		}
142
143
		$this->logRequest( 'delete', $key, $server, $result );
144
		return $result;
145
	}
146
147
	public function getMulti( array $keys, $flags = 0 ) {
148
		$batches = [];
149
		$conns = [];
150 View Code Duplication
		foreach ( $keys as $key ) {
151
			list( $server, $conn ) = $this->getConnection( $key );
152
			if ( !$conn ) {
153
				continue;
154
			}
155
			$conns[$server] = $conn;
156
			$batches[$server][] = $key;
157
		}
158
		$result = [];
159
		foreach ( $batches as $server => $batchKeys ) {
160
			$conn = $conns[$server];
161
			try {
162
				$conn->multi( Redis::PIPELINE );
163
				foreach ( $batchKeys as $key ) {
164
					$conn->get( $key );
165
				}
166
				$batchResult = $conn->exec();
167
				if ( $batchResult === false ) {
168
					$this->debug( "multi request to $server failed" );
169
					continue;
170
				}
171
				foreach ( $batchResult as $i => $value ) {
172
					if ( $value !== false ) {
173
						$result[$batchKeys[$i]] = $this->unserialize( $value );
174
					}
175
				}
176
			} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
177
				$this->handleException( $conn, $e );
178
			}
179
		}
180
181
		$this->debug( "getMulti for " . count( $keys ) . " keys " .
182
			"returned " . count( $result ) . " results" );
183
		return $result;
184
	}
185
186
	/**
187
	 * @param array $data
188
	 * @param int $expiry
189
	 * @return bool
190
	 */
191
	public function setMulti( array $data, $expiry = 0 ) {
192
		$batches = [];
193
		$conns = [];
194 View Code Duplication
		foreach ( $data as $key => $value ) {
195
			list( $server, $conn ) = $this->getConnection( $key );
196
			if ( !$conn ) {
197
				continue;
198
			}
199
			$conns[$server] = $conn;
200
			$batches[$server][] = $key;
201
		}
202
203
		$expiry = $this->convertToRelative( $expiry );
204
		$result = true;
205
		foreach ( $batches as $server => $batchKeys ) {
206
			$conn = $conns[$server];
207
			try {
208
				$conn->multi( Redis::PIPELINE );
209
				foreach ( $batchKeys as $key ) {
210
					if ( $expiry ) {
211
						$conn->setex( $key, $expiry, $this->serialize( $data[$key] ) );
212
					} else {
213
						$conn->set( $key, $this->serialize( $data[$key] ) );
214
					}
215
				}
216
				$batchResult = $conn->exec();
217
				if ( $batchResult === false ) {
218
					$this->debug( "setMulti request to $server failed" );
219
					continue;
220
				}
221
				foreach ( $batchResult as $value ) {
222
					if ( $value === false ) {
223
						$result = false;
224
					}
225
				}
226
			} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
227
				$this->handleException( $server, $conn, $e );
228
				$result = false;
229
			}
230
		}
231
232
		return $result;
233
	}
234
235 View Code Duplication
	public function add( $key, $value, $expiry = 0 ) {
236
		list( $server, $conn ) = $this->getConnection( $key );
237
		if ( !$conn ) {
238
			return false;
239
		}
240
		$expiry = $this->convertToRelative( $expiry );
241
		try {
242
			if ( $expiry ) {
243
				$result = $conn->set(
244
					$key,
245
					$this->serialize( $value ),
246
					[ 'nx', 'ex' => $expiry ]
247
				);
248
			} else {
249
				$result = $conn->setnx( $key, $this->serialize( $value ) );
250
			}
251
		} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
252
			$result = false;
253
			$this->handleException( $conn, $e );
254
		}
255
256
		$this->logRequest( 'add', $key, $server, $result );
257
		return $result;
258
	}
259
260
	/**
261
	 * Non-atomic implementation of incr().
262
	 *
263
	 * Probably all callers actually want incr() to atomically initialise
264
	 * values to zero if they don't exist, as provided by the Redis INCR
265
	 * command. But we are constrained by the memcached-like interface to
266
	 * return null in that case. Once the key exists, further increments are
267
	 * atomic.
268
	 * @param string $key Key to increase
269
	 * @param int $value Value to add to $key (Default 1)
270
	 * @return int|bool New value or false on failure
271
	 */
272 View Code Duplication
	public function incr( $key, $value = 1 ) {
273
		list( $server, $conn ) = $this->getConnection( $key );
274
		if ( !$conn ) {
275
			return false;
276
		}
277
		try {
278
			if ( !$conn->exists( $key ) ) {
279
				return null;
280
			}
281
			// @FIXME: on races, the key may have a 0 TTL
282
			$result = $conn->incrBy( $key, $value );
283
		} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
284
			$result = false;
285
			$this->handleException( $conn, $e );
286
		}
287
288
		$this->logRequest( 'incr', $key, $server, $result );
289
		return $result;
290
	}
291
292 View Code Duplication
	public function changeTTL( $key, $expiry = 0 ) {
293
		list( $server, $conn ) = $this->getConnection( $key );
294
		if ( !$conn ) {
295
			return false;
296
		}
297
298
		$expiry = $this->convertToRelative( $expiry );
299
		try {
300
			$result = $conn->expire( $key, $expiry );
301
		} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
302
			$result = false;
303
			$this->handleException( $conn, $e );
304
		}
305
306
		$this->logRequest( 'expire', $key, $server, $result );
307
		return $result;
308
	}
309
310
	public function modifySimpleRelayEvent( array $event ) {
311
		if ( array_key_exists( 'val', $event ) ) {
312
			$event['val'] = serialize( $event['val'] ); // this class uses PHP serialization
313
		}
314
315
		return $event;
316
	}
317
318
	/**
319
	 * @param mixed $data
320
	 * @return string
321
	 */
322
	protected function serialize( $data ) {
323
		// Serialize anything but integers so INCR/DECR work
324
		// Do not store integer-like strings as integers to avoid type confusion (bug 60563)
325
		return is_int( $data ) ? $data : serialize( $data );
326
	}
327
328
	/**
329
	 * @param string $data
330
	 * @return mixed
331
	 */
332
	protected function unserialize( $data ) {
333
		$int = intval( $data );
334
		return $data === (string)$int ? $int : unserialize( $data );
335
	}
336
337
	/**
338
	 * Get a Redis object with a connection suitable for fetching the specified key
339
	 * @param string $key
340
	 * @return array (server, RedisConnRef) or (false, false)
341
	 */
342
	protected function getConnection( $key ) {
343
		$candidates = array_keys( $this->serverTagMap );
344
345
		if ( count( $this->servers ) > 1 ) {
346
			ArrayUtils::consistentHashSort( $candidates, $key, '/' );
347
			if ( !$this->automaticFailover ) {
348
				$candidates = array_slice( $candidates, 0, 1 );
349
			}
350
		}
351
352
		while ( ( $tag = array_shift( $candidates ) ) !== null ) {
353
			$server = $this->serverTagMap[$tag];
354
			$conn = $this->redisPool->getConnection( $server, $this->logger );
355
			if ( !$conn ) {
356
				continue;
357
			}
358
359
			// If automatic failover is enabled, check that the server's link
360
			// to its master (if any) is up -- but only if there are other
361
			// viable candidates left to consider. Also, getMasterLinkStatus()
362
			// does not work with twemproxy, though $candidates will be empty
363
			// by now in such cases.
364
			if ( $this->automaticFailover && $candidates ) {
365
				try {
366
					if ( $this->getMasterLinkStatus( $conn ) === 'down' ) {
367
						// If the master cannot be reached, fail-over to the next server.
368
						// If masters are in data-center A, and replica DBs in data-center B,
369
						// this helps avoid the case were fail-over happens in A but not
370
						// to the corresponding server in B (e.g. read/write mismatch).
371
						continue;
372
					}
373
				} catch ( RedisException $e ) {
0 ignored issues
show
The class RedisException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
374
					// Server is not accepting commands
375
					$this->handleException( $conn, $e );
376
					continue;
377
				}
378
			}
379
380
			return [ $server, $conn ];
381
		}
382
383
		$this->setLastError( BagOStuff::ERR_UNREACHABLE );
384
385
		return [ false, false ];
386
	}
387
388
	/**
389
	 * Check the master link status of a Redis server that is configured as a replica DB.
390
	 * @param RedisConnRef $conn
391
	 * @return string|null Master link status (either 'up' or 'down'), or null
392
	 *  if the server is not a replica DB.
393
	 */
394
	protected function getMasterLinkStatus( RedisConnRef $conn ) {
395
		$info = $conn->info();
0 ignored issues
show
Documentation Bug introduced by
The method info does not exist on object<RedisConnRef>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
396
		return isset( $info['master_link_status'] )
397
			? $info['master_link_status']
398
			: null;
399
	}
400
401
	/**
402
	 * Log a fatal error
403
	 * @param string $msg
404
	 */
405
	protected function logError( $msg ) {
406
		$this->logger->error( "Redis error: $msg" );
407
	}
408
409
	/**
410
	 * The redis extension throws an exception in response to various read, write
411
	 * and protocol errors. Sometimes it also closes the connection, sometimes
412
	 * not. The safest response for us is to explicitly destroy the connection
413
	 * object and let it be reopened during the next request.
414
	 * @param RedisConnRef $conn
415
	 * @param Exception $e
416
	 */
417
	protected function handleException( RedisConnRef $conn, $e ) {
418
		$this->setLastError( BagOStuff::ERR_UNEXPECTED );
419
		$this->redisPool->handleError( $conn, $e );
420
	}
421
422
	/**
423
	 * Send information about a single request to the debug log
424
	 * @param string $method
425
	 * @param string $key
426
	 * @param string $server
427
	 * @param bool $result
428
	 */
429
	public function logRequest( $method, $key, $server, $result ) {
430
		$this->debug( "$method $key on $server: " .
431
			( $result === false ? "failure" : "success" ) );
432
	}
433
}
434