diff options
author | Stanislav Malyshev <stas@php.net> | 2011-06-26 21:34:39 +0000 |
---|---|---|
committer | Stanislav Malyshev <stas@php.net> | 2011-06-26 21:34:39 +0000 |
commit | b158091ed6f725e443b9a49fe7212596bd1c218b (patch) | |
tree | db532b642ba48ae2eb70ccb7ed84f32444d30c96 /ext/standard/crypt_blowfish.c | |
parent | dace636cc984260f51e6b03df476f8cc81a81969 (diff) | |
download | php-git-b158091ed6f725e443b9a49fe7212596bd1c218b.tar.gz |
Fix crypt_blowfish 8-bit chars problem (CVE-2011-2483), add tests
# See details at http://www.openwall.com/lists/announce/2011/06/21/1
Diffstat (limited to 'ext/standard/crypt_blowfish.c')
-rw-r--r-- | ext/standard/crypt_blowfish.c | 74 |
1 files changed, 66 insertions, 8 deletions
diff --git a/ext/standard/crypt_blowfish.c b/ext/standard/crypt_blowfish.c index 6c99a396d5..9e3a9241d0 100644 --- a/ext/standard/crypt_blowfish.c +++ b/ext/standard/crypt_blowfish.c @@ -1,5 +1,5 @@ /* - $Id$ + $Id$ */ /* * This code comes from John the Ripper password cracker, with reentrant @@ -7,6 +7,7 @@ * cracking removed. * * Written by Solar Designer <solar at openwall.com> in 1998-2002 and + * placed in the public domain. Quick self-test added in 2011 and also * placed in the public domain. * * There's absolutely no warranty. @@ -51,6 +52,13 @@ #define __CONST __const #endif +/* + * Please keep this enabled. We really don't want incompatible hashes to be + * produced. The performance cost of this quick self-test is around 0.6% at + * the "$2a$08" setting. + */ +#define BF_SELF_TEST + #ifdef __i386__ #define BF_ASM 0 #define BF_SCALE 1 @@ -63,6 +71,7 @@ #endif typedef unsigned int BF_word; +typedef signed int BF_word_signed; /* Number of Blowfish rounds, this is also hardcoded into a few places */ #define BF_N 16 @@ -555,7 +564,7 @@ static void BF_swap(BF_word *x, int count) } while (ptr < &data.ctx.S[3][0xFF]); #endif -static void BF_set_key(__CONST char *key, BF_key expanded, BF_key initial) +static void BF_set_key(__CONST char *key, BF_key expanded, BF_key initial, int sign_extension_bug) { __CONST char *ptr = key; int i, j; @@ -565,7 +574,10 @@ static void BF_set_key(__CONST char *key, BF_key expanded, BF_key initial) tmp = 0; for (j = 0; j < 4; j++) { tmp <<= 8; - tmp |= *ptr; + if (sign_extension_bug) + tmp |= (BF_word_signed)(signed char)*ptr; + else + tmp |= (unsigned char)*ptr; if (!*ptr) ptr = key; else ptr++; } @@ -575,8 +587,9 @@ static void BF_set_key(__CONST char *key, BF_key expanded, BF_key initial) } } -char *php_crypt_blowfish_rn(__CONST char *key, __CONST char *setting, - char *output, int size) +static char *BF_crypt(__CONST char *key, __CONST char *setting, + char *output, int size, + BF_word min) { #if BF_ASM extern void _BF_body_r(BF_ctx *ctx); @@ -602,7 +615,7 @@ char *php_crypt_blowfish_rn(__CONST char *key, __CONST char *setting, if (setting[0] != '$' || setting[1] != '2' || - setting[2] != 'a' || + (setting[2] != 'a' && setting[2] != 'x') || setting[3] != '$' || setting[4] < '0' || setting[4] > '3' || setting[5] < '0' || setting[5] > '9' || @@ -613,7 +626,7 @@ char *php_crypt_blowfish_rn(__CONST char *key, __CONST char *setting, } count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); - if (count < 16 || BF_decode(data.binary.salt, &setting[7], 16)) { + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { clean(data.binary.salt, sizeof(data.binary.salt)); __set_errno(EINVAL); return NULL; @@ -621,7 +634,7 @@ char *php_crypt_blowfish_rn(__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, data.expanded_key, data.ctx.P, setting[2] == 'x'); memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); @@ -721,14 +734,59 @@ char *php_crypt_blowfish_rn(__CONST char *key, __CONST char *setting, BF_encode(&output[7 + 22], data.binary.output, 23); output[7 + 22 + 31] = '\0'; + #ifndef BF_SELF_TEST /* Overwrite the most obvious sensitive data we have on the stack. Note * that this does not guarantee there's no sensitive data left on the * stack and/or in registers; I'm not aware of portable code that does. */ clean(&data, sizeof(data)); +#endif return output; } +char *php_crypt_blowfish_rn(__CONST char *key, __CONST char *setting, + char *output, int size) +{ +#ifdef BF_SELF_TEST + __CONST char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + __CONST char *test_2a = + "$2a$00$abcdefghijklmnopqrstuui1D709vfamulimlGcq0qq3UvuUasvEa" + "\0" + "canary"; + __CONST char *test_2x = + "$2x$00$abcdefghijklmnopqrstuuVUrPmXD6q/nVSSp7pNDhCR9071IfIRe" + "\0" + "canary"; + __CONST char *test_hash, *p; + int ok; + char buf[7 + 22 + 31 + 1 + 6 + 1]; + + output = BF_crypt(key, setting, output, size, 16); + +/* Do a quick self-test. This also happens to overwrite BF_crypt()'s data. */ + test_hash = (setting[2] == 'x') ? test_2x : test_2a; + memcpy(buf, test_hash, sizeof(buf)); + memset(buf, -1, sizeof(buf) - (6 + 1)); /* keep "canary" only */ + p = BF_crypt(test_key, test_hash, buf, sizeof(buf) - 6, 1); + + ok = (p == buf && !memcmp(p, test_hash, sizeof(buf))); + +/* This could reveal what hash type we were using last. Unfortunately, we + * can't reliably clean the test_hash pointer. */ + clean(&buf, sizeof(buf)); + + if (ok) + return output; + +/* Should not happen */ + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +#else +#warning Self-test is disabled, please enable + return BF_crypt(key, setting, output, size, 16); +#endif +} + char *php_crypt_gensalt_blowfish_rn(unsigned long count, __CONST char *input, int size, char *output, int output_size) { |