From 9e23d9cf901504c0a9c949f1d8ebded0d8675bd9 Mon Sep 17 00:00:00 2001
From: Nikita Malyavin <nikitamalyavin@gmail.com>
Date: Wed, 4 Sep 2024 19:57:45 +0200
Subject: [PATCH] MDEV-34854 Parsec sends garbage when using an empty password

When an empty password is set, the server doesn't call
st_mysql_auth::hash_password and leaves
MYSQL_SERVER_AUTH_INFO::auth_string empty.

Normally, auth_string would be a string of readable characters beginnging from
"P", followed by a single digit, like "P0". In this case, auth_string[0] will
be '\0', followed by a garbage.

In case of empty auth_string, let's generate an empty password ad-hoc. It makes
no sense save it to auth_string, since it's a sopy of a globally stored object.
This means also, that ext-salt will be random per each login.
---
 mysql-test/suite/plugins/r/parsec.result | 15 ++++++++++
 mysql-test/suite/plugins/t/parsec.test   | 14 +++++++++
 plugin/auth_parsec/server_parsec.cc      | 37 +++++++++++++++++-------
 3 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/mysql-test/suite/plugins/r/parsec.result b/mysql-test/suite/plugins/r/parsec.result
index 512c066e2d7..362a4533941 100644
--- a/mysql-test/suite/plugins/r/parsec.result
+++ b/mysql-test/suite/plugins/r/parsec.result
@@ -28,3 +28,18 @@ test.have_ssl()
 yes
 drop function have_ssl;
 drop user test1@'%';
+# MDEV-34854 Parsec sends garbage when using an empty password
+create user test2@'%' identified via parsec using PASSWORD('');
+show grants for test2@'%';
+Grants for test2@%
+GRANT USAGE ON *.* TO `test2`@`%` IDENTIFIED VIA parsec
+connect con4, localhost, test2,;
+select 4, USER(), CURRENT_USER();
+4	USER()	CURRENT_USER()
+4	test2@localhost	test2@%
+disconnect con4;
+connect(localhost,test2,wrong_pwd,test,MASTER_MYPORT,MASTER_MYSOCK);
+connect con5, localhost, test2, "wrong_pwd";
+ERROR 28000: Access denied for user 'test2'@'localhost' (using password: NO)
+connection default;
+drop user test2@'%';
diff --git a/mysql-test/suite/plugins/t/parsec.test b/mysql-test/suite/plugins/t/parsec.test
index 2374d66dac4..e5fc04058eb 100644
--- a/mysql-test/suite/plugins/t/parsec.test
+++ b/mysql-test/suite/plugins/t/parsec.test
@@ -43,3 +43,17 @@ if ($MTR_COMBINATION_WIN) {
 
 drop function have_ssl;
 drop user test1@'%';
+
+
+--echo # MDEV-34854 Parsec sends garbage when using an empty password
+create user test2@'%' identified via parsec using PASSWORD('');
+show grants for test2@'%';
+connect con4, localhost, test2,;
+select 4, USER(), CURRENT_USER();
+disconnect con4;
+
+--replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT
+--error ER_ACCESS_DENIED_ERROR
+connect con5, localhost, test2, "wrong_pwd";
+connection default;
+drop user test2@'%';
diff --git a/plugin/auth_parsec/server_parsec.cc b/plugin/auth_parsec/server_parsec.cc
index 7a0d2a172d6..21ff5abdb9e 100644
--- a/plugin/auth_parsec/server_parsec.cc
+++ b/plugin/auth_parsec/server_parsec.cc
@@ -166,6 +166,24 @@ int ed25519_derive_public_key(const uchar *raw_private_key, uchar *pub_key)
 #endif
 }
 
+static int hash_password(const char *password, size_t password_length,
+                         Passwd_in_memory *derivation)
+{
+  derivation->algorithm= 'P';
+  derivation->iterations= 0;
+  int err= my_random_bytes(derivation->salt, sizeof(derivation->salt));
+  if (err != MY_AES_OK)
+    return 1;
+
+  uchar derived_key[PBKDF2_HASH_LENGTH];
+  if (compute_derived_key(password, password_length, derivation, derived_key))
+    return 1;
+
+  if (ed25519_derive_public_key(derived_key, derivation->pub_key))
+    return 1;
+  return 0;
+}
+
 static
 int hash_password(const char *password, size_t password_length,
                   char *hash, size_t *hash_length)
@@ -175,15 +193,7 @@ int hash_password(const char *password, size_t password_length,
     return 1;
 
   Passwd_in_memory memory;
-  memory.algorithm= 'P';
-  memory.iterations= 0;
-  my_random_bytes(memory.salt, sizeof(memory.salt));
-
-  uchar derived_key[PBKDF2_HASH_LENGTH];
-  if (compute_derived_key(password, password_length, &memory, derived_key))
-    return 1;
-
-  if (ed25519_derive_public_key(derived_key, memory.pub_key))
+  if (hash_password(password, password_length, &memory))
     return 1;
 
   stored->algorithm= memory.algorithm;
@@ -260,7 +270,14 @@ int auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
 
   auto passwd= (Passwd_in_memory*)info->auth_string;
 
-  if (vio->write_packet(vio, (uchar*)info->auth_string, 2 + CHALLENGE_SALT_LENGTH))
+  Passwd_in_memory zero_passwd_derivation;
+  if (info->auth_string_length == 0) // Empty passwd
+  {
+    hash_password("", 0, &zero_passwd_derivation);
+    passwd= &zero_passwd_derivation;
+  }
+
+  if (vio->write_packet(vio, (uchar*)passwd, 2 + CHALLENGE_SALT_LENGTH))
     return CR_ERROR;
 
   Client_signed_response *client_response;
-- 
2.30.9