Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ PHP NEWS
while COW violation flag is still set). (alexandre-daubois)
. Added form feed (\f) in the default trimmed characters of trim(), rtrim()
and ltrim(). (Weilin Du)
. Fixed bug GH-21673 (the bundled bcrypt implementation no longer truncates
passwords at NUL bytes, and password_hash()/password_verify() no longer
reject them). (Weilin Du)
. Invalid mode values now throw in array_filter() instead of being silently
defaulted to 0. (Jorg Sowa)
. Fixed bug GH-21058 (error_log() crashes with message_type 3 and
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/crypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch

memset(output, 0, PHP_MAX_SALT_LEN + 1);

crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output));
crypt_res = php_crypt_blowfish_rn(password, pass_len, salt, output, sizeof(output));
if (!crypt_res) {
ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
return NULL;
Expand Down
47 changes: 26 additions & 21 deletions ext/standard/crypt_blowfish.c
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This file is a vendored library and must not be touched.

Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,10 @@ static void BF_swap(BF_word *x, int count)
*(ptr - 1) = R; \
} while (ptr < &data.ctx.S[3][0xFF]);

static void BF_set_key(const char *key, BF_key expanded, BF_key initial,
unsigned char flags)
static void BF_set_key(const char *key, size_t key_len, BF_key expanded,
BF_key initial, unsigned char flags)
{
const char *ptr = key;
size_t key_pos = 0;
unsigned int bug, i, j;
BF_word safety, sign, diff, tmp[2];

Expand All @@ -559,8 +559,7 @@ static void BF_set_key(const char *key, BF_key expanded, BF_key initial,
* information - that is, we mostly use fixed-cost bitwise operations instead
* of branches or table lookups. (One conditional branch based on password
* length remains. It is not part of the bug aftermath, though, and is
* difficult and possibly unreasonable to avoid given the use of C strings by
* the caller, which results in similar timing leaks anyway.)
* difficult and possibly unreasonable to avoid here.)
*
* For actual implementation, we set an array index in the variable "bug"
* (0 means no bug, 1 means sign extension bug emulation) and a flag in the
Expand All @@ -577,13 +576,20 @@ static void BF_set_key(const char *key, BF_key expanded, BF_key initial,

sign = diff = 0;

/*
* bcrypt cycles over the password bytes plus a trailing NUL terminator.
* The explicit length keeps embedded NUL bytes significant while
* preserving the historical behavior for ordinary C strings.
*/
for (i = 0; i < BF_N + 2; i++) {
tmp[0] = tmp[1] = 0;
for (j = 0; j < 4; j++) {
unsigned char c = key_pos < key_len ? (unsigned char) key[key_pos] : 0;

tmp[0] <<= 8;
tmp[0] |= (unsigned char)*ptr; /* correct */
tmp[0] |= c; /* correct */
tmp[1] <<= 8;
tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */
tmp[1] |= (BF_word_signed)(signed char)c; /* bug */
/*
* Sign extension in the first char has no effect - nothing to overwrite yet,
* and those extra 24 bits will be fully shifted out of the 32-bit word. For
Expand All @@ -592,10 +598,9 @@ static void BF_set_key(const char *key, BF_key expanded, BF_key initial,
*/
if (j)
sign |= tmp[1] & 0x80;
if (!*ptr)
ptr = key;
else
ptr++;
key_pos++;
if (key_pos > key_len)
key_pos = 0;
Comment thread
LamentXU123 marked this conversation as resolved.
Outdated
}
diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */

Expand Down Expand Up @@ -636,7 +641,7 @@ static const unsigned char flags_by_subtype[26] =
{2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0};

static char *BF_crypt(const char *key, const char *setting,
static char *BF_crypt(const char *key, size_t key_len, const char *setting,
char *output, int size,
BF_word min)
{
Expand Down Expand Up @@ -679,7 +684,7 @@ static char *BF_crypt(const char *key, const char *setting,
}
BF_swap(data.binary.salt, 4);

BF_set_key(key, data.expanded_key, data.ctx.P,
BF_set_key(key, key_len, data.expanded_key, data.ctx.P,
flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']);

memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S));
Expand Down Expand Up @@ -800,10 +805,10 @@ static int _crypt_output_magic(const char *setting, char *output, int size)
* The performance cost of this quick self-test is around 0.6% at the "$2a$08"
* setting.
*/
char *php_crypt_blowfish_rn(const char *key, const char *setting,
char *output, int size)
char *php_crypt_blowfish_rn(const char *key, size_t key_len,
const char *setting, char *output, int size)
{
const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8";
static const char test_key[] = "8b \xd0\xc1\xd2\xcf\xcc\xd8";
const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu";
static const char * const test_hashes[2] =
{"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */
Expand All @@ -819,7 +824,7 @@ char *php_crypt_blowfish_rn(const char *key, const char *setting,

/* Hash the supplied password */
_crypt_output_magic(setting, output, size);
retval = BF_crypt(key, setting, output, size, 16);
retval = BF_crypt(key, key_len, setting, output, size, 16);
save_errno = errno;

/*
Expand All @@ -838,17 +843,17 @@ char *php_crypt_blowfish_rn(const char *key, const char *setting,
}
memset(buf.o, 0x55, sizeof(buf.o));
buf.o[sizeof(buf.o) - 1] = 0;
p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1);
p = BF_crypt(test_key, sizeof(test_key) - 1, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1);

ok = (p == buf.o &&
!memcmp(p, buf.s, 7 + 22) &&
!memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1));

{
const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345";
static const char k[] = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345";
BF_key ae, ai, ye, yi;
BF_set_key(k, ae, ai, 2); /* $2a$ */
BF_set_key(k, ye, yi, 4); /* $2y$ */
BF_set_key(k, sizeof(k) - 1, ae, ai, 2); /* $2a$ */
BF_set_key(k, sizeof(k) - 1, ye, yi, 4); /* $2y$ */
ai[0] ^= 0x10000; /* undo the safety (for comparison) */
ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 &&
!memcmp(ae, ye, sizeof(ae)) &&
Expand Down
6 changes: 4 additions & 2 deletions ext/standard/crypt_blowfish.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
#ifndef _CRYPT_BLOWFISH_H
#define _CRYPT_BLOWFISH_H

extern char *php_crypt_blowfish_rn(const char *key, const char *setting,
char *output, int size);
#include <stddef.h>

extern char *php_crypt_blowfish_rn(const char *key, size_t key_len,
const char *setting, char *output, int size);

#endif
5 changes: 0 additions & 5 deletions ext/standard/password.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,6 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a
zval *zcost;
zend_long cost = PHP_PASSWORD_BCRYPT_COST;

if (memchr(ZSTR_VAL(password), '\0', ZSTR_LEN(password))) {
zend_value_error("Bcrypt password must not contain null character");
return NULL;
}

if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
cost = zval_get_long(zcost);
}
Expand Down
7 changes: 0 additions & 7 deletions ext/standard/tests/password/password_bcrypt_errors.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,7 @@ try {
} catch (ValueError $exception) {
echo $exception->getMessage() . "\n";
}

try {
var_dump(password_hash("null\0password", PASSWORD_BCRYPT));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Invalid bcrypt cost parameter specified: 3
Invalid bcrypt cost parameter specified: 32
Bcrypt password must not contain null character
24 changes: 24 additions & 0 deletions ext/standard/tests/password/password_bcrypt_null_verify.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
password_* handles bcrypt passwords containing null bytes
--SKIPIF--
<?php
$setting = '$2y$05$CCCCCCCCCCCCCCCCCCCCC.';
if (crypt("foo\0bar", $setting) === crypt("foo", $setting)) {
die("skip bcrypt backend truncates NUL bytes");
}
?>
--FILE--
<?php
$password = "foo\0bar";
$hash = password_hash($password, PASSWORD_BCRYPT);

var_dump($hash !== false);
var_dump(password_verify($password, $hash));
var_dump(password_verify("foo", $hash));
var_dump(password_verify("foo\0baz", $hash));
?>
--EXPECT--
bool(true)
bool(true)
bool(false)
bool(false)
22 changes: 22 additions & 0 deletions ext/standard/tests/strings/crypt_blowfish_null.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
crypt() bcrypt distinguishes passwords containing null bytes
--SKIPIF--
<?php
$setting = '$2y$05$CCCCCCCCCCCCCCCCCCCCC.';
if (crypt("foo\0bar", $setting) === crypt("foo", $setting)) {
die("skip bcrypt backend truncates NUL bytes");
}
Comment thread
LamentXU123 marked this conversation as resolved.
Outdated
?>
--FILE--
<?php
$setting = '$2y$05$CCCCCCCCCCCCCCCCCCCCC.';
$hash = crypt("foo\0bar", $setting);

var_dump($hash === crypt("foo\0bar", $hash));
var_dump($hash === crypt("foo", $hash));
var_dump($hash === crypt("foo\0baz", $hash));
?>
--EXPECT--
bool(true)
bool(false)
bool(false)
Loading