Skip to content

Commit

Permalink
Bug #32036194: MYSQL41 AND AUTO_CONT.SET_AUTH_DATA("TOOSHORT");
Browse files Browse the repository at this point in the history
A heap-buffer-overflow in libmyqlxclient when
- auth-method is MYSQL41
- the "server" sends a nonce that is shortert than 20 bytes.

==2466857==ERROR: AddressSanitizer: heap-buffer-overflow on address
#0 0x4a7b76 in memcpy (routertest_component_routing_splicer+0x4a7b76)
#1 0x7fd3a1d89052 in SHA1_Update (/libcrypto.so.1.1+0x1c2052)
#2 0x63409c in compute_mysql41_hash_multi(unsigned char*, char const*,
   unsigned int, char const*, unsigned int)
   ...

RB: 25305
Reviewed-by: Lukasz Kotula <lukasz.kotula@oracle.com>
  • Loading branch information
Grzegorz Szwarc committed Oct 21, 2020
1 parent 9a8ee9f commit c93069e
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 28 deletions.
38 changes: 20 additions & 18 deletions plugin/x/client/authentication/password_hasher.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
Expand Down Expand Up @@ -44,14 +44,14 @@ namespace {

const char *_dig_vec_upper = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

void compute_two_stage_mysql41_hash(const char *password, size_t pass_len,
void compute_two_stage_mysql41_hash(const std::string &password,
uint8_t *hash_stage1,
uint8_t *hash_stage2) {
/* Stage 1: hash pwd */
compute_mysql41_hash(hash_stage1, password, static_cast<unsigned>(pass_len));
compute_mysql41_hash(hash_stage1, password.c_str(), password.size());

/* Stage 2 : hash first stage's output. */
compute_mysql41_hash(hash_stage2, (const char *)hash_stage1,
compute_mysql41_hash(hash_stage2, reinterpret_cast<const char *>(hash_stage1),
MYSQL41_HASH_SIZE);
}

Expand Down Expand Up @@ -108,7 +108,7 @@ std::string generate_user_salt() {
char *buffer = &result[0];
char *end = buffer + result.length() - 1;

RAND_bytes((unsigned char *)buffer, SCRAMBLE_LENGTH);
RAND_bytes(reinterpret_cast<unsigned char *>(buffer), SCRAMBLE_LENGTH);

/* Sequence must be a legal UTF8 string */
for (; buffer < end; buffer++) {
Expand All @@ -119,29 +119,31 @@ std::string generate_user_salt() {
return result;
}

bool check_scramble_mysql41_hash(const char *scramble_arg, const char *message,
bool check_scramble_mysql41_hash(const std::string &scramble_arg,
const std::string &message,
const uint8_t *hash_stage2) {
char buf[MYSQL41_HASH_SIZE];
uint8_t hash_stage2_reassured[MYSQL41_HASH_SIZE];

DBUG_ASSERT(MYSQL41_HASH_SIZE == SCRAMBLE_LENGTH);
/* create key to encrypt scramble */
compute_mysql41_hash_multi(reinterpret_cast<uint8_t *>(buf), message,
SCRAMBLE_LENGTH, (const char *)hash_stage2,
MYSQL41_HASH_SIZE);
compute_mysql41_hash_multi(
reinterpret_cast<uint8_t *>(buf), message.c_str(), message.size(),
reinterpret_cast<const char *>(hash_stage2), MYSQL41_HASH_SIZE);

/* encrypt scramble */
my_crypt(buf, (const uint8_t *)buf, (const uint8_t *)scramble_arg,
my_crypt(buf, reinterpret_cast<const uint8_t *>(buf),
reinterpret_cast<const uint8_t *>(scramble_arg.c_str()),
SCRAMBLE_LENGTH);

/* now buf supposedly contains hash_stage1: so we can get hash_stage2 */
compute_mysql41_hash(reinterpret_cast<uint8_t *>(hash_stage2_reassured),
(const char *)buf, MYSQL41_HASH_SIZE);
reinterpret_cast<const char *>(buf), MYSQL41_HASH_SIZE);

return 0 == memcmp(hash_stage2, hash_stage2_reassured, MYSQL41_HASH_SIZE);
}

std::string scramble(const char *message, const char *password) {
std::string scramble(const std::string &message, const std::string &password) {
uint8_t hash_stage1[MYSQL41_HASH_SIZE];
uint8_t hash_stage2[MYSQL41_HASH_SIZE];
std::string result(SCRAMBLE_LENGTH, '\0');
Expand All @@ -151,16 +153,16 @@ std::string scramble(const char *message, const char *password) {
DBUG_ASSERT(MYSQL41_HASH_SIZE == SCRAMBLE_LENGTH);

/* Two stage SHA1 hash of the pwd */
compute_two_stage_mysql41_hash(password, strlen(password),
compute_two_stage_mysql41_hash(password,
reinterpret_cast<uint8_t *>(hash_stage1),
reinterpret_cast<uint8_t *>(hash_stage2));

/* create crypt string as sha1(message, hash_stage2) */
compute_mysql41_hash_multi(reinterpret_cast<uint8_t *>(&result[0]), message,
SCRAMBLE_LENGTH, (const char *)hash_stage2,
MYSQL41_HASH_SIZE);
my_crypt(&result[0], (const uint8_t *)&result[0], hash_stage1,
SCRAMBLE_LENGTH);
compute_mysql41_hash_multi(
reinterpret_cast<uint8_t *>(&result[0]), message.c_str(), message.size(),
reinterpret_cast<const char *>(hash_stage2), MYSQL41_HASH_SIZE);
my_crypt(&result[0], reinterpret_cast<const uint8_t *>(&result[0]),
hash_stage1, SCRAMBLE_LENGTH);

return result;
}
Expand Down
7 changes: 4 additions & 3 deletions plugin/x/client/authentication/password_hasher.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
Expand Down Expand Up @@ -33,8 +33,9 @@ namespace password_hasher {

char *octet2hex(char *to, const char *str, size_t len);
std::string generate_user_salt();
std::string scramble(const char *message, const char *password);
bool check_scramble_mysql41_hash(const char *scramble_arg, const char *message,
std::string scramble(const std::string &message, const std::string &password);
bool check_scramble_mysql41_hash(const std::string &scramble_arg,
const std::string &message,
const uint8_t *hash_stage2);
std::string get_password_from_salt(const std::string &hash_stage2);

Expand Down
11 changes: 5 additions & 6 deletions plugin/x/client/xprotocol_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -325,24 +325,23 @@ XError Protocol_impl::authenticate_mysql41(const std::string &user,
XError operator()(
const std::string &user, const std::string &pass, const std::string &db,
const Mysqlx::Session::AuthenticateContinue &auth_continue) {
std::string data;
std::string password_hash;

Mysqlx::Session::AuthenticateContinue auth_continue_response;

if (pass.length()) {
password_hash = password_hasher::scramble(
auth_continue.auth_data().c_str(), pass.c_str());
password_hash =
password_hasher::scramble(auth_continue.auth_data(), pass);
password_hash = password_hasher::get_password_from_salt(password_hash);

if (password_hash.empty()) {
return XError{CR_UNKNOWN_ERROR, ER_TEXT_HASHING_FUNCTION_FAILED};
}
}

std::string data;
data.append(db).push_back('\0'); // authz
data.append(user).push_back('\0'); // authc
data.append(password_hash); // pass

Mysqlx::Session::AuthenticateContinue auth_continue_response;
auth_continue_response.set_auth_data(data);

return m_protocol->send(auth_continue_response);
Expand Down
19 changes: 18 additions & 1 deletion unittest/gunit/xplugin/xcl/protocol_auth_t.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
Expand Down Expand Up @@ -142,6 +142,23 @@ TEST_F(Xcl_protocol_impl_tests_auth,
assert_authenticate("PLAIN", expected_error_code);
}

TEST_F(Xcl_protocol_impl_tests_auth,
execute_authenticate_mysql41_method_too_short_auth_data) {
auto msg_auth_cont_s =
Server_message<Auth_details::Authenticate_continue>::make_required();
msg_auth_cont_s.set_auth_data("TOOSHORT");

{
InSequence s;
expect_write_message(m_messages.msg_auth_start_mysql41.get());
expect_read_message(msg_auth_cont_s);
expect_write_message(m_messages.msg_auth_cont_mysql41.get());
expect_read_message_without_payload(Auth_details::Authenticate_ok());
}

assert_authenticate("MYSQL41");
}

TEST_P(Xcl_protocol_impl_tests_auth,
execute_authenticate_challenge_response_method) {
auto msg_auth_cont_s =
Expand Down

0 comments on commit c93069e

Please sign in to comment.