CVE-2020-13249: Insufficient Information (NVD-CWE-noinfo)
value: 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.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 8.8
Published: 2020-05-20T19:15Z
Repository: https://github.com/mariadb-corporation/mariadb-connector-c
Language: C
Files changed: libmariadb/mariadb_lib.c
Commit date: 2020-05-07 15:06:32 +0200
Commit message: sanity checks for client-supplied OK packet content

reported by Matthias Kaiser, Apple Information Security

Diff: diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c
index 0434527..8c2a99b 100644
--- a/libmariadb/mariadb_lib.c
+++ b/libmariadb/mariadb_lib.c
@@ -79,10 +79,12 @@
 #endif
 
 #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 */
 extern ulong net_buffer_length;  /* net.c */
 
@@ -2137,67 +2139,73 @@ mysql_send_query(MYSQL* mysql, const char* query, unsigned long length)
   return ma_simple_command(mysql, COM_QUERY, query, length, 1,0);
 }
 
 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);
   mysql->server_status=uint2korr(pos);
   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)
     {
       ma_clear_session_state(mysql);
       pos+= item_len;
 
       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:
             case SESSION_TRACK_STATE_CHANGE:
             case SESSION_TRACK_TRANSACTION_CHARACTERISTICS:
             case SESSION_TRACK_SYSTEM_VARIABLES:
               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);
               pos+= plen;
               session_item->data= str;
@@ -2216,45 +2224,44 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
                 my_bool set_charset= 0;
                 /* make sure that we update charset in case it has changed */
                 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;
                 }
               }
               break;
             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++)
         {
           mysql->extension->session_state[i].list= list_reverse(mysql->extension->session_state[i].list);
@@ -2265,10 +2272,20 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
   }
   /* CONC-351: clear session state information */
   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)
 {
   uchar *pos;

Vulnerable code:   if (pos < mysql->net.read_pos+length)
        if (pos < mysql->net.read_pos + length)
          size_t item_len= net_field_length(&pos);  /* length for all items */
          while (item_len > 0)
            old_pos= pos;
              {
                ma_clear_session_state(mysql);
                SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
                return -1;
              }
                {
                  ma_clear_session_state(mysql);
                  SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
                  return -1;
                }
                if (set_charset &&
                  char cs_name[64];
                  MARIADB_CHARSET_INFO *cs_info;
                  if ((cs_info = (MARIADB_CHARSET_INFO *)mysql_find_charset_name(cs_name)))
            item_len-= (pos - old_pos);
Fixed code: #define CHARSET_NAME_LEN 64

  uchar *end= mysql->net.read_pos+length;
  if (pos > end)
    goto corrupted;
  if (pos < end)
    if (pos + item_len > end)
      goto corrupted;
        if (pos < end)

          item_len= net_field_length(&pos);  /* length for all items */
          if (pos + item_len > end)
            goto corrupted;
          end= pos + item_len;
          while (pos < end)
              if (pos + plen > end)
                goto corrupted;
                  goto oom;
                if (pos + plen > end)
                  goto corrupted;
                  goto oom;
                if (set_charset && str->length < CHARSET_NAME_LEN &&
                  char cs_name[CHARSET_NAME_LEN];
                  const MARIADB_CHARSET_INFO *cs_info;
                  if ((cs_info = mysql_find_charset_name(cs_name)))
              if (pos + plen > end)
                goto corrupted;

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;