From 1b8322c3c6f406bd119145c6f38ddeb802e1b9fc Mon Sep 17 00:00:00 2001
From: Alexey Kopytov <Alexey.Kopytov@sun.com>
Date: Wed, 27 May 2009 14:20:57 +0400
Subject: [PATCH] Bug #44767: invalid memory reads in password() and           
  old_password() functions The PASSWORD() and OLD_PASSWORD() functions could
 lead to memory reads outside of an internal buffer when used with BLOB
 arguments.

String::c_ptr() assumes there is at least one extra byte
in the internally allocated buffer when adding the trailing
'\0'.  This, however, may not be the case when a String object
was initialized with externally allocated buffer.

The bug was fixed by adding an additional "length" argument to
make_scrambled_password_323() and make_scrambled_password() in
order to avoid String::c_ptr() calls for
PASSWORD()/OLD_PASSWORD().

However, since the make_scrambled_password[_323] functions are
a part of the client library ABI, the functions with the new
interfaces were implemented with the 'my_' prefix in their
names, with the old functions changed to be wrappers around
the new ones to maintain interface compatibility.

mysql-test/r/func_crypt.result:
  Added a test case for bug #44767.
mysql-test/t/func_crypt.test:
  Added a test case for bug #44767.
sql/item_strfunc.cc:
  Use the new my_make_scrambled_password*() to avoid
  String::c_ptr().
sql/item_strfunc.h:
  Changed Item_func[_old]_password::alloc() interfaces so that
  we can use the new my_make_scrambled_password*() functions.
sql/mysql_priv.h:
  Added declarations for the new my_make_scrambled_password*()
  functions.
sql/password.c:
  Added new my_make_scrambled_password*() functions with an
  additional "length" argument. Changed ones to be wrappers
  around the new ones to maintain interface compatibility.
sql/sql_yacc.yy:
  Utilize the new password hashing functions with additional length
  argument.
---
 mysql-test/r/func_crypt.result | 11 +++++++
 mysql-test/t/func_crypt.test   | 12 ++++++++
 sql/item_strfunc.cc            | 14 +++++----
 sql/item_strfunc.h             |  4 +--
 sql/mysql_priv.h               |  6 ++++
 sql/password.c                 | 55 ++++++++++++++++++++++++++++------
 sql/sql_yacc.yy                | 11 +++----
 7 files changed, 91 insertions(+), 22 deletions(-)

diff --git a/mysql-test/r/func_crypt.result b/mysql-test/r/func_crypt.result
index afdec0f4d06..42fa18b17b2 100644
--- a/mysql-test/r/func_crypt.result
+++ b/mysql-test/r/func_crypt.result
@@ -95,3 +95,14 @@ Note	1003	select password(_latin1'idkfa ') AS `password('idkfa ')`,old_password(
 select encrypt('1234','_.');
 encrypt('1234','_.')
 #
+#
+# Bug #44767: invalid memory reads in password() and old_password() 
+#             functions
+#
+CREATE TABLE t1(c1 MEDIUMBLOB);
+INSERT INTO t1 VALUES (REPEAT('a', 1024));
+SELECT OLD_PASSWORD(c1), PASSWORD(c1) FROM t1;
+OLD_PASSWORD(c1)	PASSWORD(c1)
+77023ffe214c04ff	*82E58A2C08AAFE72C8EB523069CD8ADB33F78F58
+DROP TABLE t1;
+End of 5.0 tests
diff --git a/mysql-test/t/func_crypt.test b/mysql-test/t/func_crypt.test
index cc3cdb9564d..6dedeaa0fef 100644
--- a/mysql-test/t/func_crypt.test
+++ b/mysql-test/t/func_crypt.test
@@ -56,3 +56,15 @@ explain extended select password('idkfa '), old_password('idkfa');
 select encrypt('1234','_.');
 
 # End of 4.1 tests
+
+--echo #
+--echo # Bug #44767: invalid memory reads in password() and old_password() 
+--echo #             functions
+--echo #
+
+CREATE TABLE t1(c1 MEDIUMBLOB);
+INSERT INTO t1 VALUES (REPEAT('a', 1024));
+SELECT OLD_PASSWORD(c1), PASSWORD(c1) FROM t1;
+DROP TABLE t1;
+
+--echo End of 5.0 tests
diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc
index bc2dcb9c61b..71d3a34cd27 100644
--- a/sql/item_strfunc.cc
+++ b/sql/item_strfunc.cc
@@ -1554,16 +1554,17 @@ String *Item_func_password::val_str(String *str)
     return 0;
   if (res->length() == 0)
     return &my_empty_string;
-  make_scrambled_password(tmp_value, res->c_ptr());
+  my_make_scrambled_password(tmp_value, res->ptr(), res->length());
   str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH, res->charset());
   return str;
 }
 
-char *Item_func_password::alloc(THD *thd, const char *password)
+char *Item_func_password::alloc(THD *thd, const char *password,
+                                size_t pass_len)
 {
   char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1);
   if (buff)
-    make_scrambled_password(buff, password);
+    my_make_scrambled_password(buff, password, pass_len);
   return buff;
 }
 
@@ -1577,16 +1578,17 @@ String *Item_func_old_password::val_str(String *str)
     return 0;
   if (res->length() == 0)
     return &my_empty_string;
-  make_scrambled_password_323(tmp_value, res->c_ptr());
+  my_make_scrambled_password_323(tmp_value, res->ptr(), res->length());
   str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH_323, res->charset());
   return str;
 }
 
-char *Item_func_old_password::alloc(THD *thd, const char *password)
+char *Item_func_old_password::alloc(THD *thd, const char *password,
+                                    size_t pass_len)
 {
   char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1);
   if (buff)
-    make_scrambled_password_323(buff, password);
+    my_make_scrambled_password_323(buff, password, pass_len);
   return buff;
 }
 
diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h
index 1c5346ab074..f2cb4d2d32a 100644
--- a/sql/item_strfunc.h
+++ b/sql/item_strfunc.h
@@ -281,7 +281,7 @@ public:
   String *val_str(String *str);
   void fix_length_and_dec() { max_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; }
   const char *func_name() const { return "password"; }
-  static char *alloc(THD *thd, const char *password);
+  static char *alloc(THD *thd, const char *password, size_t pass_len);
 };
 
 
@@ -300,7 +300,7 @@ public:
   String *val_str(String *str);
   void fix_length_and_dec() { max_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; } 
   const char *func_name() const { return "old_password"; }
-  static char *alloc(THD *thd, const char *password);
+  static char *alloc(THD *thd, const char *password, size_t pass_len);
 };
 
 
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 9768668f103..55bcd30999d 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -1677,6 +1677,12 @@ extern void turn_parser_debug_on();
 SQL_CRYPT *get_crypt_for_frm(void);
 #endif
 
+/* password.c */
+extern "C" void my_make_scrambled_password_323(char *to, const char *password,
+                                               size_t pass_len);
+extern "C" void my_make_scrambled_password(char *to, const char *password,
+                                           size_t pass_len);
+
 #include "sql_view.h"
 
 /* Some inline functions for more speed */
diff --git a/sql/password.c b/sql/password.c
index 57ed3e6ab0f..e12074549a4 100644
--- a/sql/password.c
+++ b/sql/password.c
@@ -137,16 +137,35 @@ void hash_password(ulong *result, const char *password, uint password_len)
     Create password to be stored in user database from raw string
     Used for pre-4.1 password handling
   SYNOPSIS
-    make_scrambled_password_323()
+    my_make_scrambled_password_323()
     to        OUT store scrambled password here
     password  IN  user-supplied password
+    pass_len  IN  length of password string
+*/
+
+void my_make_scrambled_password_323(char *to, const char *password,
+                                    size_t pass_len)
+{
+  ulong hash_res[2];
+  hash_password(hash_res, password, (uint) pass_len);
+  sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]);
+}
+
+
+/*
+  Wrapper around my_make_scrambled_password_323() to maintain client lib ABI
+  compatibility.
+  In server code usage of my_make_scrambled_password_323() is preferred to
+  avoid strlen().
+  SYNOPSIS
+    make_scrambled_password_323()
+    to        OUT store scrambled password here
+    password  IN  NULL-terminated string with user-supplied password
 */
 
 void make_scrambled_password_323(char *to, const char *password)
 {
-  ulong hash_res[2];
-  hash_password(hash_res, password, (uint) strlen(password));
-  sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]);
+  my_make_scrambled_password_323(to, password, strlen(password));
 }
 
 
@@ -383,20 +402,21 @@ my_crypt(char *to, const uchar *s1, const uchar *s2, uint len)
     The result of this function is used as return value from PASSWORD() and
     is stored in the database.
   SYNOPSIS
-    make_scrambled_password()
+    my_make_scrambled_password()
     buf       OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string
-    password  IN  NULL-terminated password string
+    password  IN  password string
+    pass_len  IN  length of password string
 */
 
-void
-make_scrambled_password(char *to, const char *password)
+void my_make_scrambled_password(char *to, const char *password,
+                                size_t pass_len)
 {
   SHA1_CONTEXT sha1_context;
   uint8 hash_stage2[SHA1_HASH_SIZE];
 
   mysql_sha1_reset(&sha1_context);
   /* stage 1: hash password */
-  mysql_sha1_input(&sha1_context, (uint8 *) password, (uint) strlen(password));
+  mysql_sha1_input(&sha1_context, (uint8 *) password, (uint) pass_len);
   mysql_sha1_result(&sha1_context, (uint8 *) to);
   /* stage 2: hash stage1 output */
   mysql_sha1_reset(&sha1_context);
@@ -409,6 +429,23 @@ make_scrambled_password(char *to, const char *password)
 }
   
 
+/*
+  Wrapper around my_make_scrambled_password() to maintain client lib ABI
+  compatibility.
+  In server code usage of my_make_scrambled_password() is preferred to
+  avoid strlen().
+  SYNOPSIS
+    make_scrambled_password()
+    buf       OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string
+    password  IN  NULL-terminated password string
+*/
+
+void make_scrambled_password(char *to, const char *password)
+{
+  my_make_scrambled_password(to, password, strlen(password));
+}
+
+
 /*
     Produce an obscure octet sequence from password and random
     string, recieved from the server. This sequence corresponds to the
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 61020a3eed0..b6a215a2306 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -10092,15 +10092,16 @@ text_or_password:
 	| PASSWORD '(' TEXT_STRING ')'
 	  {
 	    $$= $3.length ? YYTHD->variables.old_passwords ?
-	        Item_func_old_password::alloc(YYTHD, $3.str) :
-	        Item_func_password::alloc(YYTHD, $3.str) :
+	        Item_func_old_password::alloc(YYTHD, $3.str, $3.length) :
+	        Item_func_password::alloc(YYTHD, $3.str, $3.length) :
 	      $3.str;
             if ($$ == NULL)
               MYSQL_YYABORT;
 	  }
 	| OLD_PASSWORD '(' TEXT_STRING ')'
 	  {
-	    $$= $3.length ? Item_func_old_password::alloc(YYTHD, $3.str) :
+	    $$= $3.length ? Item_func_old_password::alloc(YYTHD, $3.str, 
+							  $3.length) :
 	      $3.str;
             if ($$ == NULL)
               MYSQL_YYABORT;
@@ -10545,7 +10546,7 @@ grant_user:
                  (char *) YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1);
                if (buff == NULL)
                  MYSQL_YYABORT;
-               make_scrambled_password_323(buff, $4.str);
+               my_make_scrambled_password_323(buff, $4.str, $4.length);
                $1->password.str= buff;
                $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323;
              }
@@ -10555,7 +10556,7 @@ grant_user:
                  (char *) YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1);
                if (buff == NULL)
                  MYSQL_YYABORT;
-               make_scrambled_password(buff, $4.str);
+               my_make_scrambled_password(buff, $4.str, $4.length);
                $1->password.str= buff;
                $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH;
              }