Bug #13856 Junk contents of third argument to mysql_options trips up mysql_real_connect.
Submitted: 7 Oct 2005 19:44 Modified: 16 Nov 2005 11:12
Reporter: Andrew Stone Email Updates:
Status: No Feedback Impact on me:
None 
Category:MySQL Server Severity:S5 (Performance)
Version:3.23.58 OS:Linux (Linux)
Assigned to: CPU Architecture:Any

[7 Oct 2005 19:44] Andrew Stone
Description:
Take a look at this code:

  char timeout_value[128];
  char mysql_statement[1024];
  provider_struct *providers;

  /* Initialize our MySQL connection */
  if (!(ucc_cluster = mysql_init(ucc_cluster))) {
    fprintf(stderr, "\nUnable to initialize MySQL DB object, quitting.\n");
    return(NULL);
  }

  /* Set the MySQL options */
  memset(timeout_value, 0, sizeof(timeout_value));
  sprintf(timeout_value, "%d", UCC_TIMEOUT);
  mysql_options(ucc_cluster, MYSQL_OPT_CONNECT_TIMEOUT, timeout_value);

  /* Establish the MySQL connection */
  if (!mysql_real_connect(ucc_cluster, UCC_PROVIDER_DB_RW_HOST,  UCC_PROVIDER_DB_RW_USER,  UCC_PROVIDER_DB_RW_PASS,  UCC_PROVIDER_DB_NAME,
                         UCC_PROVIDER_DB_RW_PORT, NULL, 0)) {
    fprintf(stderr, "\nUnable to connect to MySQL DB, quitting.  Error: %s, Code:%d\n",
            mysql_error(ucc_cluster), mysql_errno(ucc_cluster));
    return(NULL);
  }

If timeout_value contains "10\0" followed by nonzero arbitrary data it may trip up the call to mysql_real_connect().  The only way to ensure that this does not happen is the call to memset() that I inserted.  Shouldn't mysql_options() be able to accept a null terminated string regardless of what is after the null without hosing a subsequent attempt to connect?

How to repeat:
Insert the above code into a C program.  Make sure the first three characters of timeout_value are '1', '0' and '\0'.  Since this is a NUL terminated string it should be enough to make mysql_options work correctly.  However if the remaining characters in timeout_value are not zeros the subsequent call to mysql_real_connect may fail.

Suggested fix:
Make mysql_options() read the needed number of NUL terminated strings from its third argument and ignore the rest.  The contents of the remaining space in the third argument to mysql_options() should not cause a subsequent call to mysql_real_connect() to fail.
[11 Oct 2005 9:47] Valeriy Kravchuk
Thank you for a problem report. Please, provide a complete test case (all the code, not only a snippet) - looks like you have it already.

Please, also inform about the MySQL server version and compiler used.
[11 Oct 2005 21:43] Andrew Stone
The MySQL DB version for this bug is 3.23.58.  The C code for this bug was compiled under gcc version 3.3.3 20040412 (Red Hat Linux 3.3.3-7).  The complete C function that this bug occured in is as follows:

provider_struct *load_providers(void) {
  int i = 0;
  int j = 0;
  int account_fields = 0;
  int account_count = 0;
  int provider_count = 0;
  MYSQL_ROW provider_row = NULL;
  MYSQL_ROW account_row = NULL;
  MYSQL_RES *ucc_provider_result = NULL;
  MYSQL_RES *ucc_account_result = NULL;
 char timeout_value[128];
  char mysql_statement[1024];
  provider_struct *providers;

  /* Initialize our MySQL connection */
  if (!(ucc_cluster = mysql_init(ucc_cluster))) {
    fprintf(stderr, "\nUnable to initialize MySQL DB object, quitting.\n");
    return(NULL);
  }

  /* Set the MySQL options */
  //memset(timeout_value, 0, sizeof(timeout_value));
  sprintf(timeout_value, "%d", UCC_TIMEOUT);
  mysql_options(ucc_cluster, MYSQL_OPT_CONNECT_TIMEOUT, timeout_value);

  /* Establish the MySQL connection */
  if (!mysql_real_connect(ucc_cluster, UCC_PROVIDER_DB_RW_HOST,  UCC_PROVIDER_DB_RW_USER,  UCC_PROVIDER_DB_RW_PASS,  UCC_PROVIDER_DB_NAME,
                         UCC_PROVIDER_DB_RW_PORT, NULL, 0)) {
    fprintf(stderr, "\nUnable to connect to MySQL DB, quitting.  Error: %s, Code:%d\n",
            mysql_error(ucc_cluster), mysql_errno(ucc_cluster));
    return(NULL);
  }

  /* get the number of providers */
  mysql_query(ucc_cluster, "SELECT id FROM provider");
  ucc_provider_result = mysql_store_result(ucc_cluster);
  provider_count = mysql_num_rows(ucc_provider_result);

  /* Now allocate the array of provider structs */
  providers = malloc(sizeof(provider_struct) * provider_count);

  /* Initialize the provider data structs */

  for (i = 0; i < provider_count; i++) {
    sprintf(mysql_statement, "SELECT * FROM provider where id = '%d'", i + 1);
    mysql_query(ucc_cluster, mysql_statement);
    ucc_provider_result = mysql_store_result(ucc_cluster);

    if ((provider_row = mysql_fetch_row(ucc_provider_result))) {
      providers[i].id = atoi(provider_row[0]);
      strlcpy(providers[i].name, provider_row[1], sizeof(providers[i].name));
      strlcpy(providers[i].address, provider_row[2], sizeof(providers[i].address));
      providers[i].active = atoi(provider_row[3]);

      /* Figure out how many accounts this provider has */
      sprintf(mysql_statement, "SELECT * FROM account where provider_id = '%d'", i + 1);
      mysql_query(ucc_cluster, mysql_statement);
      ucc_account_result =   mysql_store_result(ucc_cluster);
      if ((account_fields = mysql_num_fields(ucc_account_result)) != 7) {
        fprintf(stderr, "\nIncorrect number of fields returned from account table.  Expected 7, got %d.  Quitting.\n", account_fields);
        return (NULL);
      }
      account_count = mysql_num_rows(ucc_account_result);
      providers[i].accounts = y_safe_malloc(account_count * sizeof(account_struct));
      /* clear the memory for consistency */
      memset(providers[i].accounts, 0, account_count * sizeof(account_struct));
      account_row = mysql_fetch_row(ucc_account_result);
      for (j = 0; j < account_count; j++) {
        providers[i].accounts[j].id = atoi(account_row[0]);
        strlcpy(providers[i].accounts[j].user, account_row[1], sizeof(providers[i].accounts[j].user));
        strlcpy(providers[i].accounts[j].pass, account_row[2], sizeof(providers[i].accounts[j].pass));
        providers[i].accounts[j].num_conns = atoi(account_row[3]);
        providers[i].accounts[j].active = atoi(account_row[5]);
        providers[i].accounts[j].num_conns = atoi(account_row[6]);
      }
    }
  }

  return providers;

}
[16 Oct 2005 11:08] Valeriy Kravchuk
Simplified test case

Attachment: 13856.c (text/x-csrc), 593 bytes.

[16 Oct 2005 11:12] Valeriy Kravchuk
Please, take a look at the minimal simplified test case I had uploaded as a file 13856.c. When I build a program out of it, as usual, and runs the results, it gives me two rows with client and server version, like these:

[openxs@Fedora 4.1]$ CFG=/home/openxs/DBS/4.1/bin/mysql_config
[openxs@Fedora 4.1]$ gcc -o 13856 `$CFG --cflags` 13856.c `$CFG --libs`
[openxs@Fedora 4.1]$ export LD_LIBRARY_PATH=`pwd`/lib/mysql
[openxs@Fedora 4.1]$ ./13856
Client info: 4.1.16
Server info: 4.1.16-debug

Note, that there is no memset() call in the text. If I understood you right, this test case will not work in your system. Please, try and inform about the results. Or, just add any needed modifications that show the problem you described.
[17 Nov 2005 0:00] Bugs System
No feedback was provided for this bug for over a month, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".