Bug #83297 Unsafe usage of setlocale()
Submitted: 7 Oct 2016 8:16 Modified: 10 Sep 2019 19:15
Reporter: Jacques Germishuys Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / ODBC Severity:S2 (Serious)
Version:5.1.18 OS:Windows
Assigned to: CPU Architecture:Any

[7 Oct 2016 8:16] Jacques Germishuys
Description:
There are a number calls to setlocale() post the initialisation phase. The following Visual Studio bug report has a good synopsis of the problem:

https://connect.microsoft.com/VisualStudio/feedback/details/790530/crt-function-setlocale-...

Basically it is not safe to "just" call setlocale() in a multi-threaded applications.

How to repeat:
1. Compile with the Visual Studio 2013 compiler
2. Execute statements from multiple threads

Suggested fix:
Use _confighreadlocale to limit the locale change to the current thread. The following patch has worked for us:

diff -u -r mysql-connector-odbc-5.1.13-src-old/driver/execute.c mysql-connector-odbc-5.1.13-src/driver/execute.c
--- driver/execute.c	2016-10-07 07:09:40.000000000 +0200
+++ driver/execute.c	2016-10-07 08:47:25.000000000 +0200
@@ -188,6 +188,7 @@
     uint i,length, had_info= 0;
     NET *net;
     SQLRETURN rc= SQL_SUCCESS;
+    int loc;
 
     int mutex_was_locked= pthread_mutex_trylock(&stmt->dbc->lock);
 
@@ -196,6 +197,10 @@
 
     if (!stmt->dbc->ds->dont_use_set_locale)
     {
+      #ifdef _WIN32
+      loc = _configthreadlocale(0);
+      _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+      #endif
       setlocale(LC_NUMERIC, "C");  /* force use of '.' as decimal point */
     }
 
@@ -274,6 +279,9 @@
     if (!stmt->dbc->ds->dont_use_set_locale)
     {
         setlocale(LC_NUMERIC,default_locale);
+        #ifdef _WIN32
+        _configthreadlocale(loc);
+        #endif
     }
 
     if (finalquery!=NULL)
@@ -289,8 +297,12 @@
     /* ! was _already_ locked, when we tried to lock */
     if (!mutex_was_locked)
       pthread_mutex_unlock(&stmt->dbc->lock);
-    if (!stmt->dbc->ds->dont_use_set_locale)
+    if (!stmt->dbc->ds->dont_use_set_locale) {
         setlocale(LC_NUMERIC,default_locale);
+        #ifdef _WIN32
+        _configthreadlocale(loc);
+        #endif
+	}
     return rc;
 }
 
diff -u -r mysql-connector-odbc-5.1.13-src-old/driver/results.c mysql-connector-odbc-5.1.13-src/driver/results.c
--- driver/results.c	2016-10-07 07:09:40.000000000 +0200
+++ driver/results.c	2016-10-07 08:46:15.000000000 +0200
@@ -1202,6 +1202,7 @@
     SQLRETURN result;
     ulong length= 0;
     DESCREC *irrec;
+    int loc;
 
     if (!stmt->result || !stmt->current_values)
     {
@@ -1230,16 +1231,25 @@
     if (!length && stmt->current_values[ColumnNumber])
       length= strlen(stmt->current_values[ColumnNumber]);
 
-    if (!stmt->dbc->ds->dont_use_set_locale)
+    if (!stmt->dbc->ds->dont_use_set_locale) {
+      #ifdef _WIN32
+      loc = _configthreadlocale(0);
+      _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+      #endif
       setlocale(LC_NUMERIC, "C");
+    }
 
     result= sql_get_data(stmt, TargetType, ColumnNumber,
                          TargetValuePtr, BufferLength, StrLen_or_IndPtr,
                          stmt->current_values[ColumnNumber], length,
                          desc_get_rec(stmt->ard, ColumnNumber, FALSE));
 
-    if (!stmt->dbc->ds->dont_use_set_locale)
+    if (!stmt->dbc->ds->dont_use_set_locale) {
         setlocale(LC_NUMERIC,default_locale);
+        #ifdef _WIN32
+        _configthreadlocale(loc);
+        #endif
+    }
 
     return result;
 }
@@ -1493,6 +1503,7 @@
     MYSQL_ROW_OFFSET  save_position;
     SQLULEN           dummy_pcrow;
     BOOL              disconnected= FALSE;
+    int               loc;
 
     LINT_INIT(save_position);
 
@@ -1633,6 +1644,10 @@
 
     if (!stmt->dbc->ds->dont_use_set_locale)
     {
+      #ifdef _WIN32
+      loc = _configthreadlocale(0);
+      _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+      #endif
       setlocale(LC_NUMERIC, "C");
     }
 
@@ -1783,9 +1798,12 @@
         stmt->end_of_set= mysql_row_seek(stmt->result,save_position);
     }
 
-    if (!stmt->dbc->ds->dont_use_set_locale)
-
+    if (!stmt->dbc->ds->dont_use_set_locale) {
         setlocale(LC_NUMERIC,default_locale);
+        #ifdef _WIN32
+        _configthreadlocale(loc);
+        #endif
+	}
 
     if (SQL_SUCCEEDED(res)
       && stmt->rows_found_in_set < stmt->ard->array_size)
[7 Oct 2016 8:18] Jacques Germishuys
This only affects Windows
[7 Oct 2016 9:42] Chiranjeevi Battula
Hello Jacques Germishuys,

Thank you for the bug report.
Verified based on internal discussion with dev's.

Thanks,
Chiranjeevi.
[20 May 2019 9:14] MySQL Verification Team
workaround:  add ;NO_LOCALE=1   to the connection string.
[10 Sep 2019 19:15] Philip Olson
Posted by developer:
 
Fixed as of the upcoming MySQL Connector/ODBC 8.0.18 release, and here's the changelog entry:

On Windows, fixed direct setlocale() usage for multi-threaded applications. 

The workaround was to add ;NO_LOCALE=1 to the connection string. 

Thanks to Jacques Germishuys for the patch.

Thank you for the bug report.