}
// hash_equals function only exists in PHP >= 5.6.0
+// http://php.net/hash_equals
if ( !function_exists( 'hash_equals' ) ) {
/**
- * Check whether a user-provided string is equal to a fixed-length secret without
- * revealing bytes of the secret through timing differences.
+ * Check whether a user-provided string is equal to a fixed-length secret string
+ * without revealing bytes of the secret string through timing differences.
*
- * This timing guarantee -- that a partial match takes the same time as a complete
- * mismatch -- is why this function is used in some security-sensitive parts of the code.
- * For example, it shouldn't be possible to guess an HMAC signature one byte at a time.
+ * The usual way to compare strings (PHP's === operator or the underlying memcmp()
+ * function in C) is to compare corresponding bytes and stop at the first difference,
+ * which would take longer for a partial match than for a complete mismatch. This
+ * is not secure when one of the strings (e.g. an HMAC or token) must remain secret
+ * and the other may come from an attacker. Statistical analysis of timing measurements
+ * over many requests may allow the attacker to guess the string's bytes one at a time
+ * (and check his guesses) even if the timing differences are extremely small.
+ *
+ * When making such a security-sensitive comparison, it is essential that the sequence
+ * in which instructions are executed and memory locations are accessed not depend on
+ * the secret string's value. HOWEVER, for simplicity, we do not attempt to minimize
+ * the inevitable leakage of the string's length. That is generally known anyway as
+ * a chararacteristic of the hash function used to compute the secret value.
*
* Longer explanation: http://www.emerose.com/timing-attacks-explained
*
* @codeCoverageIgnore
- * @param string $known_string Fixed-length secret to compare against
+ * @param string $known_string Fixed-length secret string to compare against
* @param string $user_string User-provided string
* @return bool True if the strings are the same, false otherwise
*/
return false;
}
- // Note that we do one thing PHP doesn't: try to avoid leaking information about
- // relative lengths of $known_string and $user_string, and of multiple $known_strings.
- // However, lengths may still inevitably leak through, for example, CPU cache misses.
$known_string_len = strlen( $known_string );
- $user_string_len = strlen( $user_string );
- $result = $known_string_len ^ $user_string_len;
- for ( $i = 0; $i < $user_string_len; $i++ ) {
- $result |= ord( $known_string[$i % $known_string_len] ) ^ ord( $user_string[$i] );
+ if ( $known_string_len !== strlen( $user_string ) ) {
+ return false;
+ }
+
+ $result = 0;
+ for ( $i = 0; $i < $known_string_len; $i++ ) {
+ $result |= ord( $known_string[$i] ) ^ ord( $user_string[$i] );
}
return ( $result === 0 );
* function, as all the arguments to wfShellExec can become unwieldy.
*
* @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
- * @param string $cmd Command line, properly escaped for shell.
+ * @param string|string[] $cmd If string, a properly shell-escaped command line,
+ * or an array of unescaped arguments, in which case each value will be escaped
+ * Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
* @param null|mixed &$retval Optional, will receive the program's exit code.
* (non-zero is usually failure)
* @param array $environ Optional environment variables which should be
}
/**
- * Modern version of wfWaitForSlaves(). Instead of looking at replication lag
- * and waiting for it to go down, this waits for the slaves to catch up to the
- * master position. Use this when updating very large numbers of rows, as
- * in maintenance scripts, to avoid causing too much lag. Of course, this is
- * a no-op if there are no slaves.
+ * Waits for the slaves to catch up to the master position
+ *
+ * Use this when updating very large numbers of rows, as in maintenance scripts,
+ * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
+ *
+ * By default this waits on the main DB cluster of the current wiki.
+ * If $cluster is set to "*" it will wait on all DB clusters, including
+ * external ones. If the lag being waiting on is caused by the code that
+ * does this check, it makes since to use $ifWritesSince, particularly if
+ * cluster is "*", to avoid excess overhead.
+ *
+ * Never call this function after a big DB write that is still in a transaction.
+ * This only makes sense after the possible lag inducing changes were committed.
*
* @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
* @param string|bool $wiki Wiki identifier accepted by wfGetLB
* @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
+ * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web)
* @return bool Success (able to connect and no timeouts reached)
*/
-function wfWaitForSlaves( $ifWritesSince = false, $wiki = false, $cluster = false ) {
+function wfWaitForSlaves(
+ $ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
+) {
// B/C: first argument used to be "max seconds of lag"; ignore such values
- $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : false;
+ $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null;
- if ( $cluster !== false ) {
- $lb = wfGetLBFactory()->getExternalLB( $cluster );
- } else {
- $lb = wfGetLB( $wiki );
+ if ( $timeout === null ) {
+ $timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10;
}
- // bug 27975 - Don't try to wait for slaves if there are none
- // Prevents permission error when getting master position
- if ( $lb->getServerCount() > 1 ) {
- if ( $ifWritesSince && !$lb->hasMasterConnection() ) {
- return true; // assume no writes done
- }
- $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
- if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
- return true; // no writes since the last wait
+ // Figure out which clusters need to be checked
+ $lbs = array();
+ if ( $cluster === '*' ) {
+ wfGetLBFactory()->forEachLB( function( LoadBalancer $lb ) use ( &$lbs ) {
+ $lbs[] = $lb;
+ } );
+ } elseif ( $cluster !== false ) {
+ $lbs[] = wfGetLBFactory()->getExternalLB( $cluster );
+ } else {
+ $lbs[] = wfGetLB( $wiki );
+ }
+
+ // Get all the master positions of applicable DBs right now.
+ // This can be faster since waiting on one cluster reduces the
+ // time needed to wait on the next clusters.
+ $masterPositions = array_fill( 0, count( $lbs ), false );
+ foreach ( $lbs as $i => $lb ) {
+ // bug 27975 - Don't try to wait for slaves if there are none
+ // Prevents permission error when getting master position
+ if ( $lb->getServerCount() > 1 ) {
+ if ( $ifWritesSince && !$lb->hasMasterConnection() ) {
+ continue; // assume no writes done
+ }
+ $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
+ if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
+ continue; // no writes since the last wait
+ }
+ $masterPositions[$i] = $dbw->getMasterPos();
}
- $pos = $dbw->getMasterPos();
- // The DBMS may not support getMasterPos() or the whole
- // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
- if ( $pos !== false ) {
- return $lb->waitForAll( $pos, PHP_SAPI === 'cli' ? 86400 : null );
+ }
+
+ $ok = true;
+ foreach ( $lbs as $i => $lb ) {
+ if ( $masterPositions[$i] ) {
+ // The DBMS may not support getMasterPos() or the whole
+ // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
+ $ok = $lb->waitForAll( $masterPositions[$i], $timeout ) && $ok;
}
}
- return true;
+ return $ok;
}
/**