Avoid thread-safety problem in ecpglib.
authorTom Lane
Tue, 22 Jan 2019 04:18:58 +0000 (23:18 -0500)
committerTom Lane
Tue, 22 Jan 2019 04:18:58 +0000 (23:18 -0500)
ecpglib attempts to force the LC_NUMERIC locale to "C" while reading
server output, to avoid problems with strtod() and related functions.
Historically it's just issued setlocale() calls to do that, but that
has major problems if we're in a threaded application.  setlocale()
itself is not required by POSIX to be thread-safe (and indeed is not,
on recent OpenBSD).  Moreover, its effects are process-wide, so that
we could cause unexpected results in other threads, or another thread
could change our setting.

On platforms having uselocale(), which is required by POSIX:2008,
we can avoid these problems by using uselocale() instead.  Windows
goes its own way as usual, but we can make it safe by using
_configthreadlocale().  Platforms having neither continue to use the
old code, but that should be pretty much nobody among current systems.

(Subsequent buildfarm results show that recent NetBSD versions still
lack uselocale(), but it's not a big problem because they also do not
support non-"C" settings for LC_NUMERIC.)

Back-patch of commits 8eb4a9312 and ee27584c4.

Michael Meskes and Tom Lane; thanks also to Takayuki Tsunakawa.

Discussion: https://postgr.es/m/31420.1547783697@sss.pgh.pa.us

configure
configure.in
src/include/pg_config.h.in
src/include/pg_config.h.win32
src/interfaces/ecpg/ecpglib/descriptor.c
src/interfaces/ecpg/ecpglib/execute.c
src/interfaces/ecpg/ecpglib/extern.h

index e88586bca7635ca4ece20899d1f17034a6f56b32..e4b1a2e632cf33eb948c2d8445c96ce97a035a26 100755 (executable)
--- a/configure
+++ b/configure
@@ -14870,7 +14870,7 @@ fi
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-for ac_func in cbrt clock_gettime dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate pstat pthread_is_threaded_np readlink setproctitle setsid shm_open symlink sync_file_range utime utimes wcstombs_l
+for ac_func in cbrt clock_gettime dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate pstat pthread_is_threaded_np readlink setproctitle setsid shm_open symlink sync_file_range uselocale utime utimes wcstombs_l
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
 
 # Win32 (really MinGW) support
 if test "$PORTNAME" = "win32"; then
+  for ac_func in _configthreadlocale
+do :
+  ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale"
+if test "x$ac_cv_func__configthreadlocale" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE__CONFIGTHREADLOCALE 1
+_ACEOF
+
+fi
+done
+
   ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday"
 if test "x$ac_cv_func_gettimeofday" = xyes; then :
   $as_echo "#define HAVE_GETTIMEOFDAY 1" >>confdefs.h
index 9099cb2525b58fd9fb3268516c70b49634c96b5e..7827853a15a84da1e885232a05f0dd21402914a5 100644 (file)
@@ -1568,7 +1568,31 @@ PGAC_FUNC_WCSTOMBS_L
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-AC_CHECK_FUNCS([cbrt clock_gettime dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate pstat pthread_is_threaded_np readlink setproctitle setsid shm_open symlink sync_file_range utime utimes wcstombs_l])
+AC_CHECK_FUNCS(m4_normalize([
+   cbrt
+   clock_gettime
+   dlopen
+   fdatasync
+   getifaddrs
+   getpeerucred
+   getrlimit
+   mbstowcs_l
+   memmove
+   poll
+   posix_fallocate
+   pstat
+   pthread_is_threaded_np
+   readlink
+   setproctitle
+   setsid
+   shm_open
+   symlink
+   sync_file_range
+   uselocale
+   utime
+   utimes
+   wcstombs_l
+]))
 
 AC_REPLACE_FUNCS(fseeko)
 case $host_os in
@@ -1732,6 +1756,7 @@ fi
 
 # Win32 (really MinGW) support
 if test "$PORTNAME" = "win32"; then
+  AC_CHECK_FUNCS(_configthreadlocale)
   AC_REPLACE_FUNCS(gettimeofday)
   AC_LIBOBJ(dirmod)
   AC_LIBOBJ(kill)
index 2fae82ccc5823fa44b9a629cf49b7e65b5bfdff7..287bd0d80d4fd634a2989f92a9acb2d4ac2cd214 100644 (file)
 /* Define to 1 if the system has the type `unsigned long long int'. */
 #undef HAVE_UNSIGNED_LONG_LONG_INT
 
+/* Define to 1 if you have the `uselocale' function. */
+#undef HAVE_USELOCALE
+
 /* Define to 1 if you have the `utime' function. */
 #undef HAVE_UTIME
 
 /* Define to 1 if your compiler understands __builtin_unreachable. */
 #undef HAVE__BUILTIN_UNREACHABLE
 
+/* Define to 1 if you have the `_configthreadlocale' function. */
+#undef HAVE__CONFIGTHREADLOCALE
+
 /* Define to 1 if you have __cpuid. */
 #undef HAVE__CPUID
 
index 7023a6abe6ba730b228ce0de06e0f74a8cfadafb..c33cca13cfc8786211c204c607904753ddb11047 100644 (file)
 /* Define to 1 if you have the `unsetenv' function. */
 /* #undef HAVE_UNSETENV */
 
+/* Define to 1 if you have the `uselocale' function. */
+/* #undef HAVE_USELOCALE */
+
 /* Define to 1 if you have the `utime' function. */
 #define HAVE_UTIME 1
 
 /* Define to 1 if your compiler understands __builtin_unreachable. */
 /* #undef HAVE__BUILTIN_UNREACHABLE */
 
+/* Define to 1 if you have the `_configthreadlocale' function. */
+#define HAVE__CONFIGTHREADLOCALE 1
+
 /* Define to 1 if you have __cpuid. */
 #define HAVE__CPUID 1
 
index 8fdf5606c09cf95d340b4fd8c7cde1c5729fb710..e5977705a3e252c0fb575a77648c4e8d9daf5bf8 100644 (file)
@@ -483,22 +483,45 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
    if (data_var.type != ECPGt_EORT)
    {
        struct statement stmt;
-       char       *oldlocale;
+
+       memset(&stmt, 0, sizeof stmt);
+       stmt.lineno = lineno;
 
        /* Make sure we do NOT honor the locale for numeric input */
        /* since the database gives the standard decimal point */
-       oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
+       /* (see comments in execute.c) */
+#ifdef HAVE_USELOCALE
+       stmt.clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+       if (stmt.clocale != (locale_t) 0)
+           stmt.oldlocale = uselocale(stmt.clocale);
+#else
+#ifdef HAVE__CONFIGTHREADLOCALE
+       stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+#endif
+       stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
        setlocale(LC_NUMERIC, "C");
-
-       memset(&stmt, 0, sizeof stmt);
-       stmt.lineno = lineno;
+#endif
 
        /* desperate try to guess something sensible */
        stmt.connection = ecpg_get_connection(NULL);
        ecpg_store_result(ECPGresult, index, &stmt, &data_var);
 
-       setlocale(LC_NUMERIC, oldlocale);
-       ecpg_free(oldlocale);
+#ifdef HAVE_USELOCALE
+       if (stmt.oldlocale != (locale_t) 0)
+           uselocale(stmt.oldlocale);
+       if (stmt.clocale)
+           freelocale(stmt.clocale);
+#else
+       if (stmt.oldlocale)
+       {
+           setlocale(LC_NUMERIC, stmt.oldlocale);
+           ecpg_free(stmt.oldlocale);
+       }
+#ifdef HAVE__CONFIGTHREADLOCALE
+       if (stmt.oldthreadlocale != -1)
+           _configthreadlocale(stmt.oldthreadlocale);
+#endif
+#endif
    }
    else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL)
 
index 6ef6df0ea0c7aef39faf84d0ce989d8f53cbb61e..df84d0b1c6ef51a70eaf8bfba834ec9bc36e3e07 100644 (file)
@@ -103,7 +103,12 @@ free_statement(struct statement *stmt)
    free_variable(stmt->outlist);
    ecpg_free(stmt->command);
    ecpg_free(stmt->name);
+#ifdef HAVE_USELOCALE
+   if (stmt->clocale)
+       freelocale(stmt->clocale);
+#else
    ecpg_free(stmt->oldlocale);
+#endif
    ecpg_free(stmt);
 }
 
@@ -1778,8 +1783,32 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
 
    /*
     * Make sure we do NOT honor the locale for numeric input/output since the
-    * database wants the standard decimal point
+    * database wants the standard decimal point.  If available, use
+    * uselocale() for this because it's thread-safe.  Windows doesn't have
+    * that, but it usually does have _configthreadlocale().
     */
+#ifdef HAVE_USELOCALE
+   stmt->clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+   if (stmt->clocale == (locale_t) 0)
+   {
+       ecpg_do_epilogue(stmt);
+       return false;
+   }
+   stmt->oldlocale = uselocale(stmt->clocale);
+   if (stmt->oldlocale == (locale_t) 0)
+   {
+       ecpg_do_epilogue(stmt);
+       return false;
+   }
+#else
+#ifdef HAVE__CONFIGTHREADLOCALE
+   stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+   if (stmt->oldthreadlocale == -1)
+   {
+       ecpg_do_epilogue(stmt);
+       return false;
+   }
+#endif
    stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
    if (stmt->oldlocale == NULL)
    {
@@ -1787,6 +1816,7 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
        return false;
    }
    setlocale(LC_NUMERIC, "C");
+#endif
 
 #ifdef ENABLE_THREAD_SAFETY
    ecpg_pthreads_init();
@@ -1989,8 +2019,18 @@ ecpg_do_epilogue(struct statement *stmt)
    if (stmt == NULL)
        return;
 
+#ifdef HAVE_USELOCALE
+   if (stmt->oldlocale != (locale_t) 0)
+       uselocale(stmt->oldlocale);
+#else
    if (stmt->oldlocale)
+   {
        setlocale(LC_NUMERIC, stmt->oldlocale);
+#ifdef HAVE__CONFIGTHREADLOCALE
+       _configthreadlocale(stmt->oldthreadlocale);
+#endif
+   }
+#endif
 
    free_statement(stmt);
 }
index a88f34106cceec5785b74561f04a3e10a0658f78..b55229943a8e21d4876198ac71f9cda0b35b22f1 100644 (file)
@@ -12,6 +12,9 @@
 #ifndef CHAR_BIT
 #include 
 #endif
+#ifdef LOCALE_T_IN_XLOCALE
+#include 
+#endif
 
 enum COMPAT_MODE
 {
@@ -61,7 +64,15 @@ struct statement
    bool        questionmarks;
    struct variable *inlist;
    struct variable *outlist;
+#ifdef HAVE_USELOCALE
+   locale_t    clocale;
+   locale_t    oldlocale;
+#else
    char       *oldlocale;
+#ifdef HAVE__CONFIGTHREADLOCALE
+   int         oldthreadlocale;
+#endif
+#endif
    int         nparams;
    char      **paramvalues;
    PGresult   *results;