CVE-2020-13249 (published 2020-05-20T19:15Z)

libmariadb/mariadb_lib.c in MariaDB Connector/C before 3.1.8 does not properly validate the content of an OK packet received from a server. NOTE: although mariadb_lib.c was originally based on code shipped for MySQL, this issue does not affect any MySQL components supported by Oracle.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/mariadb-corporation/mariadb-connector-c
commit_details: repo_url: https://github.com/mariadb-corporation/mariadb-connector-c
author: Sergei Golubchik
author_date: 2020-05-07 14:57:00+02:00
msg: sanity checks for client-supplied OK packet content

reported by Matthias Kaiser, Apple Information Security
repository: repo_url: https://github.com/mariadb-corporation/mariadb-connector-c
repo_name: mariadb-corporation/mariadb-connector-c
description: MariaDB Connector/C  is used to connect applications developed in C/C++ to MariaDB and MySQL databases.The client library is LGPL licensed.
repo_language: C
owner: mariadb-corporation
file_changes: filename: mariadb_lib.c
change_type: ModificationType.MODIFY
diff: @@ -81,6 +81,8 @@
 #define ASYNC_CONTEXT_DEFAULT_STACK_SIZE (4096*15)
 #define MA_RPL_VERSION_HACK "5.5.5-"
 
+#define CHARSET_NAME_LEN 64
+
 #undef max_allowed_packet
 #undef net_buffer_length
 extern ulong max_allowed_packet; /* net.c */
@@ -2139,6 +2141,7 @@ mysql_send_query(MYSQL* mysql, const char* query, unsigned long length)
 
 int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
 {
+  uchar *end= mysql->net.read_pos+length;
   size_t item_len;
   mysql->affected_rows= net_field_length_ll(&pos);
   mysql->insert_id=	  net_field_length_ll(&pos);
@@ -2146,10 +2149,14 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
   pos+=2;
   mysql->warning_count=uint2korr(pos);
   pos+=2;
-  if (pos < mysql->net.read_pos+length)
+  if (pos > end)
+    goto corrupted;
+  if (pos < end)
   {
     if ((item_len= net_field_length(&pos)))
       mysql->info=(char*) pos;
+    if (pos + item_len > end)
+      goto corrupted;
 
     /* check if server supports session tracking */
     if (mysql->server_capabilities & CLIENT_SESSION_TRACKING)
@@ -2160,23 +2167,26 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
       if (mysql->server_status & SERVER_SESSION_STATE_CHANGED)
       {
         int i;
-        if (pos < mysql->net.read_pos + length)
+        if (pos < end)
         {
           LIST *session_item;
           MYSQL_LEX_STRING *str= NULL;
           enum enum_session_state_type si_type;
           uchar *old_pos= pos;
-          size_t item_len= net_field_length(&pos);  /* length for all items */
+
+          item_len= net_field_length(&pos);  /* length for all items */
+          if (pos + item_len > end)
+            goto corrupted;
+          end= pos + item_len;
 
           /* length was already set, so make sure that info will be zero terminated */
           if (mysql->info)
             *old_pos= 0;
 
-          while (item_len > 0)
+          while (pos < end)
           {
             size_t plen;
             char *data;
-            old_pos= pos;
             si_type= (enum enum_session_state_type)net_field_length(&pos);
             switch(si_type) {
             case SESSION_TRACK_SCHEMA:
@@ -2186,16 +2196,14 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
               if (si_type != SESSION_TRACK_STATE_CHANGE)
                 net_field_length(&pos); /* ignore total length, item length will follow next */
               plen= net_field_length(&pos);
+              if (pos + plen > end)
+                goto corrupted;
               if (!(session_item= ma_multi_malloc(0,
                                   &session_item, sizeof(LIST),
                                   &str, sizeof(MYSQL_LEX_STRING),
                                   &data, plen,
                                   NULL)))
-              {
-                ma_clear_session_state(mysql);
-                SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
-                return -1;
-              }
+                  goto oom;
               str->length= plen;
               str->str= data;
               memcpy(str->str, (char *)pos, plen);
@@ -2218,30 +2226,28 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
                 if (!strncmp(str->str, "character_set_client", str->length))
                   set_charset= 1;
                 plen= net_field_length(&pos);
+                if (pos + plen > end)
+                  goto corrupted;
                 if (!(session_item= ma_multi_malloc(0,
                                     &session_item, sizeof(LIST),
                                     &str, sizeof(MYSQL_LEX_STRING),
                                     &data, plen,
                                     NULL)))
-                {
-                  ma_clear_session_state(mysql);
-                  SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
-                  return -1;
-                }
+                  goto oom;
                 str->length= plen;
                 str->str= data;
                 memcpy(str->str, (char *)pos, plen);
                 pos+= plen;
                 session_item->data= str;
                 mysql->extension->session_state[si_type].list= list_add(mysql->extension->session_state[si_type].list, session_item);
-                if (set_charset &&
+                if (set_charset && str->length < CHARSET_NAME_LEN &&
                     strncmp(mysql->charset->csname, str->str, str->length) != 0)
                 {
-                  char cs_name[64];
-                  MARIADB_CHARSET_INFO *cs_info;
+                  char cs_name[CHARSET_NAME_LEN];
+                  const MARIADB_CHARSET_INFO *cs_info;
                   memcpy(cs_name, str->str, str->length);
                   cs_name[str->length]= 0;
-                  if ((cs_info = (MARIADB_CHARSET_INFO *)mysql_find_charset_name(cs_name)))
+                  if ((cs_info = mysql_find_charset_name(cs_name)))
                     mysql->charset= cs_info;
                 }
               }
@@ -2249,10 +2255,11 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
             default:
               /* not supported yet */
               plen= net_field_length(&pos);
+              if (pos + plen > end)
+                goto corrupted;
               pos+= plen;
               break;
             }
-            item_len-= (pos - old_pos);
           }
         }
         for (i= SESSION_TRACK_BEGIN; i <= SESSION_TRACK_END; i++)
@@ -2267,6 +2274,16 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
   else if (mysql->server_capabilities & CLIENT_SESSION_TRACKING)
     ma_clear_session_state(mysql);
   return(0);
+
+oom:
+  ma_clear_session_state(mysql);
+  SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
+  return -1;
+
+corrupted:
+  ma_clear_session_state(mysql);
+  SET_CLIENT_ERROR(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0);
+  return -1;
 }
 
 int mthd_my_read_query_result(MYSQL *mysql)

programming_language: C