Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicitly require the hash extension. #8138

Open
wants to merge 5 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"issues": "https://core.trac.wordpress.org/"
},
"require": {
"ext-hash": "*",
"ext-json": "*",
"php": ">=7.2.24"
},
Expand Down
2 changes: 1 addition & 1 deletion src/wp-admin/includes/class-wp-site-health.php
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ public function get_test_php_extensions() {
),
'hash' => array(
'function' => 'hash',
'required' => false,
'required' => true,
),
'imagick' => array(
'extension' => 'imagick',
Expand Down
35 changes: 20 additions & 15 deletions src/wp-admin/includes/update-core.php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unable to test this file/code path as it takes the required versions from the new version file.

Original file line number Diff line number Diff line change
Expand Up @@ -1009,9 +1009,6 @@
* @global array $_old_requests_files
* @global array $_new_bundled_files
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $wp_version
* @global string $required_php_version
* @global string $required_mysql_version
*
* @param string $from New release unzipped path.
* @param string $to Path to old WordPress installation.
Expand Down Expand Up @@ -1075,7 +1072,7 @@ function update_core( $from, $to ) {
}

/*
* Import $wp_version, $required_php_version, and $required_mysql_version from the new version.
* Import $wp_version, $required_php_version, $required_php_extensions, and $required_mysql_version from the new version.
* DO NOT globalize any variables imported from `version-current.php` in this function.
*
* BC Note: $wp_filesystem->wp_content_dir() returned unslashed pre-2.8.
Expand Down Expand Up @@ -1181,17 +1178,25 @@ function update_core( $from, $to ) {
);
}

// Add a warning when the JSON PHP extension is missing.
if ( ! extension_loaded( 'json' ) ) {
return new WP_Error(
'php_not_compatible_json',
sprintf(
/* translators: 1: WordPress version number, 2: The PHP extension name needed. */
__( 'The update cannot be installed because WordPress %1$s requires the %2$s PHP extension.' ),
$wp_version,
'JSON'
)
);
$missing_extensions = new WP_Error();

foreach ( $required_php_extensions as $extension ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth doing an isset here in case something weird happens, such as a failed upgrade, to make sure required php extensions is defined.

It's probably worht checking when the failed upgrade is caught.

if ( ! extension_loaded( $extension ) ) {
$missing_extensions->add(
"php_not_compatible_{$extension}",
sprintf(
/* translators: 1: WordPress version number, 2: The PHP extension name needed. */
__( 'The update cannot be installed because WordPress %1$s requires the %2$s PHP extension.' ),
$wp_version,
$extension
)
);
}
}

// Add a warning when required PHP extensions are missing.
if ( $missing_extensions->has_errors() ) {
return $missing_extensions;
}

/** This filter is documented in wp-admin/includes/update-core.php */
Expand Down
30 changes: 25 additions & 5 deletions src/wp-admin/install.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,13 @@ function display_setup_form( $error = null ) {
}

/**
* @global string $wp_version The WordPress version string.
* @global string $required_php_version The required PHP version string.
* @global string $required_mysql_version The required MySQL version string.
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $wp_version The WordPress version string.
* @global string $required_php_version The required PHP version string.
* @global string[] $required_php_extensions The names of required PHP extensions.
* @global string $required_mysql_version The required MySQL version string.
* @global wpdb $wpdb WordPress database abstraction object.
*/
global $wp_version, $required_php_version, $required_mysql_version, $wpdb;
global $wp_version, $required_php_version, $required_php_extensions, $required_mysql_version, $wpdb;

$php_version = PHP_VERSION;
$mysql_version = $wpdb->db_version();
Expand Down Expand Up @@ -298,6 +299,25 @@ function display_setup_form( $error = null ) {
die( '<h1>' . __( 'Requirements Not Met' ) . '</h1><p>' . $compat . '</p></body></html>' );
}

$missing_extensions = array();

foreach ( $required_php_extensions as $extension ) {
if ( ! extension_loaded( $extension ) ) {
$missing_extensions[] = sprintf(
/* translators: 1: URL to WordPress release notes, 2: WordPress version number, 3: The PHP extension name needed. */
__( 'You cannot install because <a href="%1$s">WordPress %2$s</a> requires the %3$s PHP extension.' ),
$version_url,
$wp_version,
$extension
);
}
}

if ( count( $missing_extensions ) > 0 ) {
display_header();
die( '<h1>' . __( 'Requirements Not Met' ) . '</h1><p>' . implode( '</p><p>', $missing_extensions ) . '</p></body></html>' );
}

if ( ! is_string( $wpdb->base_prefix ) || '' === $wpdb->base_prefix ) {
display_header();
die(
Expand Down
29 changes: 22 additions & 7 deletions src/wp-admin/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@
}

/**
* @global string $wp_version The WordPress version string.
* @global string $required_php_version The required PHP version string.
* @global string $required_mysql_version The required MySQL version string.
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $wp_version The WordPress version string.
* @global string $required_php_version The required PHP version string.
* @global string[] $required_php_extensions The names of required PHP extensions.
* @global string $required_mysql_version The required MySQL version string.
* @global wpdb $wpdb WordPress database abstraction object.
*/
global $wp_version, $required_php_version, $required_mysql_version, $wpdb;
global $wp_version, $required_php_version, $required_php_extensions, $required_mysql_version, $wpdb;

$step = (int) $step;

Expand All @@ -54,6 +55,20 @@
$mysql_compat = version_compare( $mysql_version, $required_mysql_version, '>=' );
}

$missing_extensions = array();

foreach ( $required_php_extensions as $extension ) {
if ( ! extension_loaded( $extension ) ) {
$missing_extensions[] = sprintf(
/* translators: 1: URL to WordPress release notes, 2: WordPress version number, 3: The PHP extension name needed. */
__( 'You cannot install because <a href="%1$s">WordPress %2$s</a> requires the %3$s PHP extension.' ),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__( 'You cannot install because <a href="%1$s">WordPress %2$s</a> requires the %3$s PHP extension.' ),
__( 'You cannot upgrade because <a href="%1$s">WordPress %2$s</a> requires the %3$s PHP extension.' ),

I tested this by bumping the db version in version.php si it looks like the version is already running when this file is hit.

$version_url,
$wp_version,
$extension
);
}
}

header( 'Content-Type: ' . get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ) );
?>
<!DOCTYPE html>
Expand Down Expand Up @@ -126,8 +141,8 @@
}

echo '<p>' . $message . '</p>';
?>
<?php
elseif ( count( $missing_extensions ) > 0 ) :
echo '<p>' . implode( '</p><p>', $missing_extensions ) . '</p>';
else :
switch ( $step ) :
case 0:
Expand Down
7 changes: 1 addition & 6 deletions src/wp-includes/class-wp-session-tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ final public static function get_instance( $user_id ) {
* @return string A hash of the session token (a verifier).
*/
private function hash_token( $token ) {
// If ext/hash is not present, use sha1() instead.
if ( function_exists( 'hash' ) ) {
return hash( 'sha256', $token );
} else {
return sha1( $token );
}
return hash( 'sha256', $token );
}

/**
Expand Down
4 changes: 1 addition & 3 deletions src/wp-includes/class-wpdb.php
Original file line number Diff line number Diff line change
Expand Up @@ -2406,12 +2406,10 @@ public function placeholder_escape() {
static $placeholder;

if ( ! $placeholder ) {
// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
// Old WP installs may not have AUTH_SALT defined.
$salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand();

$placeholder = '{' . hash_hmac( $algo, uniqid( $salt, true ), $salt ) . '}';
$placeholder = '{' . hash_hmac( 'sha256', uniqid( $salt, true ), $salt ) . '}';
}

/*
Expand Down
112 changes: 0 additions & 112 deletions src/wp-includes/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,118 +263,6 @@ function _mb_strlen( $str, $encoding = null ) {
return --$count;
}

if ( ! function_exists( 'hash_hmac' ) ) :
/**
* Compat function to mimic hash_hmac().
*
* The Hash extension is bundled with PHP by default since PHP 5.1.2.
* However, the extension may be explicitly disabled on select servers.
* As of PHP 7.4.0, the Hash extension is a core PHP extension and can no
* longer be disabled.
* I.e. when PHP 7.4.0 becomes the minimum requirement, this polyfill
* and the associated `_hash_hmac()` function can be safely removed.
*
* @ignore
* @since 3.2.0
*
* @see _hash_hmac()
*
* @param string $algo Hash algorithm. Accepts 'md5' or 'sha1'.
* @param string $data Data to be hashed.
* @param string $key Secret key to use for generating the hash.
* @param bool $binary Optional. Whether to output raw binary data (true),
* or lowercase hexits (false). Default false.
* @return string|false The hash in output determined by `$binary`.
* False if `$algo` is unknown or invalid.
*/
function hash_hmac( $algo, $data, $key, $binary = false ) {
return _hash_hmac( $algo, $data, $key, $binary );
}
endif;

/**
* Internal compat function to mimic hash_hmac().
*
* @ignore
* @since 3.2.0
*
* @param string $algo Hash algorithm. Accepts 'md5' or 'sha1'.
* @param string $data Data to be hashed.
* @param string $key Secret key to use for generating the hash.
* @param bool $binary Optional. Whether to output raw binary data (true),
* or lowercase hexits (false). Default false.
* @return string|false The hash in output determined by `$binary`.
* False if `$algo` is unknown or invalid.
*/
function _hash_hmac( $algo, $data, $key, $binary = false ) {
$packs = array(
'md5' => 'H32',
'sha1' => 'H40',
);

if ( ! isset( $packs[ $algo ] ) ) {
return false;
}

$pack = $packs[ $algo ];

if ( strlen( $key ) > 64 ) {
$key = pack( $pack, $algo( $key ) );
}

$key = str_pad( $key, 64, chr( 0 ) );

$ipad = ( substr( $key, 0, 64 ) ^ str_repeat( chr( 0x36 ), 64 ) );
$opad = ( substr( $key, 0, 64 ) ^ str_repeat( chr( 0x5C ), 64 ) );

$hmac = $algo( $opad . pack( $pack, $algo( $ipad . $data ) ) );

if ( $binary ) {
return pack( $pack, $hmac );
}

return $hmac;
}

if ( ! function_exists( 'hash_equals' ) ) :
/**
* Timing attack safe string comparison.
*
* Compares two strings using the same time whether they're equal or not.
*
* Note: It can leak the length of a string when arguments of differing length are supplied.
*
* This function was added in PHP 5.6.
* However, the Hash extension may be explicitly disabled on select servers.
* As of PHP 7.4.0, the Hash extension is a core PHP extension and can no
* longer be disabled.
* I.e. when PHP 7.4.0 becomes the minimum requirement, this polyfill
* can be safely removed.
*
* @since 3.9.2
*
* @param string $known_string Expected string.
* @param string $user_string Actual, user supplied, string.
* @return bool Whether strings are equal.
*/
function hash_equals( $known_string, $user_string ) {
$known_string_length = strlen( $known_string );

if ( strlen( $user_string ) !== $known_string_length ) {
return false;
}

$result = 0;

// Do not attempt to "optimize" this.
for ( $i = 0; $i < $known_string_length; $i++ ) {
$result |= ord( $known_string[ $i ] ) ^ ord( $user_string[ $i ] );
}

return 0 === $result;
}
endif;

// sodium_crypto_box() was introduced in PHP 7.2.
if ( ! function_exists( 'sodium_crypto_box' ) ) {
require ABSPATH . WPINC . '/sodium_compat/autoload.php';
Expand Down
8 changes: 2 additions & 6 deletions src/wp-includes/pluggable.php
Original file line number Diff line number Diff line change
Expand Up @@ -768,9 +768,7 @@ function wp_validate_auth_cookie( $cookie = '', $scheme = '' ) {

$key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );

// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
$hash = hash_hmac( $algo, $username . '|' . $expiration . '|' . $token, $key );
$hash = hash_hmac( 'sha256', $username . '|' . $expiration . '|' . $token, $key );

if ( ! hash_equals( $hash, $hmac ) ) {
/**
Expand Down Expand Up @@ -871,9 +869,7 @@ function wp_generate_auth_cookie( $user_id, $expiration, $scheme = 'auth', $toke

$key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );

// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
$hash = hash_hmac( $algo, $user->user_login . '|' . $expiration . '|' . $token, $key );
$hash = hash_hmac( 'sha256', $user->user_login . '|' . $expiration . '|' . $token, $key );

$cookie = $user->user_login . '|' . $expiration . '|' . $token . '|' . $hash;

Expand Down
10 changes: 10 additions & 0 deletions src/wp-includes/version.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@
*/
$required_php_version = '7.2.24';

/**
* Holds the names of required PHP extensions.
*
* @global string[] $required_php_extensions
*/
$required_php_extensions = array(
'json',
'hash',
);

/**
* Holds the required MySQL version.
*
Expand Down
15 changes: 8 additions & 7 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
* include version.php from another installation and don't override
* these values if already set.
*
* @global string $wp_version The WordPress version string.
* @global int $wp_db_version WordPress database version.
* @global string $tinymce_version TinyMCE version.
* @global string $required_php_version The required PHP version string.
* @global string $required_mysql_version The required MySQL version string.
* @global string $wp_local_package Locale code of the package.
* @global string $wp_version The WordPress version string.
* @global int $wp_db_version WordPress database version.
* @global string $tinymce_version TinyMCE version.
* @global string $required_php_version The required PHP version string.
* @global string[] $required_php_extensions The names of required PHP extensions.
* @global string $required_mysql_version The required MySQL version string.
* @global string $wp_local_package Locale code of the package.
*/
global $wp_version, $wp_db_version, $tinymce_version, $required_php_version, $required_mysql_version, $wp_local_package;
global $wp_version, $wp_db_version, $tinymce_version, $required_php_version, $required_php_extensions, $required_mysql_version, $wp_local_package;
require ABSPATH . WPINC . '/version.php';
require ABSPATH . WPINC . '/compat.php';
require ABSPATH . WPINC . '/load.php';
Expand Down
Loading
Loading