| Summary: | vfprintf/cvt/__dtoa race condition in threaded programs | ||
|---|---|---|---|
| Product: | Base System | Reporter: | Tor Egge <tegge> |
| Component: | bin | Assignee: | tegge |
| Status: | Closed FIXED | ||
| Severity: | Affects Only Me | ||
| Priority: | Normal | ||
| Version: | 4.0-CURRENT | ||
| Hardware: | Any | ||
| OS: | Any | ||
One workaround for the race is to serialize vfprintf() calls that use
floating point conversion specifications.
Index: stdio/vfprintf.c
===================================================================
RCS file: /home/ncvs/src/lib/libc/stdio/vfprintf.c,v
retrieving revision 1.23
diff -u -r1.23 vfprintf.c
--- stdio/vfprintf.c 2001/01/06 20:48:00 1.23
+++ stdio/vfprintf.c 2001/01/06 23:15:45
@@ -261,6 +261,17 @@
#include <math.h>
#include "floatio.h"
+/*
+ * Serialize calls to cvt and __dtoa() and the usage of the result
+ * in threaded programs.
+ */
+
+#include "libc_private.h"
+#include "spinlock.h"
+static spinlock_t thread_lock = _SPINLOCK_INITIALIZER;
+#define THREAD_LOCK() if (__isthreaded) _SPINLOCK(&thread_lock);
+#define THREAD_UNLOCK() if (__isthreaded) _SPINUNLOCK(&thread_lock);
+
#define BUF (MAXEXP+MAXFRACT+1) /* + decimal point */
#define DEFPREC 6
@@ -271,6 +282,9 @@
#define BUF 68
+#define THREAD_LOCK()
+#define THREAD_UNLOCK()
+
#endif /* FLOATING_POINT */
#define STATIC_ARG_TBL_SIZE 8 /* Size of static argument table. */
@@ -303,6 +317,7 @@
int width; /* width from format (%8d), or 0 */
int prec; /* precision from format (%.3d), or -1 */
char sign; /* sign prefix (' ', '+', '-', or \0) */
+ int didlock; /* have obtained thread lock */
#ifdef FLOATING_POINT
char softsign; /* temporary negative sign for floats */
double _double; /* double precision arguments %[eEfgG] */
@@ -418,6 +433,7 @@
}
+ didlock = 0;
FLOCKFILE(fp);
/* sorry, fprintf(read_only_file, "") returns EOF, not 0 */
if (cantwrite(fp)) {
@@ -608,6 +624,8 @@
break;
}
flags |= FPT;
+ THREAD_LOCK();
+ didlock = 1;
cp = cvt(_double, prec, flags, &softsign,
&expt, ch, &ndig);
if (ch == 'g' || ch == 'G') {
@@ -849,6 +867,10 @@
PRINT(cp, 1);
PRINT(expstr, expsize);
}
+ if (didlock != 0) {
+ THREAD_UNLOCK();
+ didlock = 0;
+ }
}
#else
PRINT(cp, size);
@@ -865,6 +887,10 @@
done:
FLUSH();
error:
+ if (didlock != 0) {
+ THREAD_UNLOCK();
+ didlock = 0;
+ }
if (__sferror(fp))
ret = EOF;
FUNLOCKFILE(fp);
Index: stdlib/strtod.c
===================================================================
RCS file: /home/ncvs/src/lib/libc/stdlib/strtod.c,v
retrieving revision 1.3
diff -u -r1.3 strtod.c
--- stdlib/strtod.c 1996/07/12 18:55:22 1.3
+++ stdlib/strtod.c 2000/06/15 03:48:29
@@ -371,6 +371,16 @@
static Bigint *freelist[Kmax+1];
+ /*
+ * Make Balloc/Bfree thread-safe in libc for use with
+ * kernel threads.
+ */
+#include "libc_private.h"
+#include "spinlock.h"
+static spinlock_t thread_lock = _SPINLOCK_INITIALIZER;
+#define THREAD_LOCK() if (__isthreaded) _SPINLOCK(&thread_lock);
+#define THREAD_UNLOCK() if (__isthreaded) _SPINUNLOCK(&thread_lock);
+
static Bigint *
Balloc
#ifdef KR_headers
@@ -382,9 +392,12 @@
int x;
Bigint *rv;
+ THREAD_LOCK();
if ( (rv = freelist[k]) ) {
freelist[k] = rv->next;
+ THREAD_UNLOCK();
} else {
+ THREAD_UNLOCK();
x = 1 << k;
rv = (Bigint *)malloc(sizeof(Bigint) + (x-1)*sizeof(long));
rv->k = k;
@@ -403,8 +416,10 @@
#endif
{
if (v) {
+ THREAD_LOCK();
v->next = freelist[v->k];
freelist[v->k] = v;
+ THREAD_UNLOCK();
}
}
Tor.Egge@fast.no wrote: > One workaround for the race is to serialize vfprintf() calls that use > floating point conversion specifications. Umm.. I wonder if this is the cause of the 'FreeBSD libc sprintf bug' that the MySQL folks see and mention on their web site? They use libc_r and see problems with sprintf... Cheers, -Peter -- Peter Wemm - peter@FreeBSD.org; peter@yahoo-inc.com; peter@netplex.com.au "All of this is for nothing if we don't go to the stars" - JMS/B5 > Umm.. I wonder if this is the cause of the 'FreeBSD libc sprintf bug'
> that the MySQL folks see and mention on their web site? They use libc_r
> and see problems with sprintf...
Yes.
The stack trace shown by gdb on the mysql mailing list is very similar
to that generated by the sample program in this PR.
- Tor Egge
> Since the interface is purely internal, it would be better to simply
> *fix* it....
Like this ?
Index: lib/libc/stdlib/strtod.c
===================================================================
RCS file: /home/ncvs/src/lib/libc/stdlib/strtod.c,v
retrieving revision 1.3
diff -u -r1.3 strtod.c
--- lib/libc/stdlib/strtod.c 1996/07/12 18:55:22 1.3
+++ lib/libc/stdlib/strtod.c 2001/01/15 21:06:01
@@ -371,6 +371,16 @@
static Bigint *freelist[Kmax+1];
+ /*
+ * Make Balloc/Bfree thread-safe in libc for use with
+ * kernel threads.
+ */
+#include "libc_private.h"
+#include "spinlock.h"
+static spinlock_t thread_lock = _SPINLOCK_INITIALIZER;
+#define THREAD_LOCK() if (__isthreaded) _SPINLOCK(&thread_lock);
+#define THREAD_UNLOCK() if (__isthreaded) _SPINUNLOCK(&thread_lock);
+
static Bigint *
Balloc
#ifdef KR_headers
@@ -382,9 +392,12 @@
int x;
Bigint *rv;
+ THREAD_LOCK();
if ( (rv = freelist[k]) ) {
freelist[k] = rv->next;
+ THREAD_UNLOCK();
} else {
+ THREAD_UNLOCK();
x = 1 << k;
rv = (Bigint *)malloc(sizeof(Bigint) + (x-1)*sizeof(long));
rv->k = k;
@@ -403,8 +416,10 @@
#endif
{
if (v) {
+ THREAD_LOCK();
v->next = freelist[v->k];
freelist[v->k] = v;
+ THREAD_UNLOCK();
}
}
@@ -1839,10 +1854,11 @@
char *
__dtoa
#ifdef KR_headers
- (d, mode, ndigits, decpt, sign, rve)
- double d; int mode, ndigits, *decpt, *sign; char **rve;
+ (d, mode, ndigits, decpt, sign, rve, resultp)
+ double d; int mode, ndigits, *decpt, *sign; char **rve, **resultp;
#else
- (double d, int mode, int ndigits, int *decpt, int *sign, char **rve)
+ (double d, int mode, int ndigits, int *decpt, int *sign, char **rve,
+ char **resultp)
#endif
{
/* Arguments ndigits, decpt, sign are similar to those
@@ -1890,15 +1906,6 @@
Bigint *b, *b1, *delta, *mlo, *mhi, *S;
double d2, ds, eps;
char *s, *s0;
- static Bigint *result;
- static int result_k;
-
- if (result) {
- result->k = result_k;
- result->maxwds = 1 << result_k;
- Bfree(result);
- result = 0;
- }
if (word0(d) & Sign_bit) {
/* set sign for everything, including 0's and NaNs */
@@ -2057,11 +2064,8 @@
if (i <= 0)
i = 1;
}
- j = sizeof(unsigned long);
- for (result_k = 0; sizeof(Bigint) - sizeof(unsigned long) + j < i;
- j <<= 1) result_k++;
- result = Balloc(result_k);
- s = s0 = (char *)result;
+ *resultp = (char *) malloc(i);
+ s = s0 = *resultp;
if (ilim >= 0 && ilim <= Quick_max && try_quick) {
Index: lib/libc/stdio/vfprintf.c
===================================================================
RCS file: /home/ncvs/src/lib/libc/stdio/vfprintf.c,v
retrieving revision 1.23
diff -u -r1.23 vfprintf.c
--- lib/libc/stdio/vfprintf.c 2001/01/06 20:48:00 1.23
+++ lib/libc/stdio/vfprintf.c 2001/01/15 21:43:09
@@ -264,7 +264,7 @@
#define BUF (MAXEXP+MAXFRACT+1) /* + decimal point */
#define DEFPREC 6
-static char *cvt __P((double, int, int, char *, int *, int, int *));
+static char *cvt __P((double, int, int, char *, int *, int, int *, char **));
static int exponent __P((char *, int, int));
#else /* no FLOATING_POINT */
@@ -310,6 +310,7 @@
int expsize; /* character count for expstr */
int ndig; /* actual number of digits returned by cvt */
char expstr[7]; /* buffer for exponent string */
+ char *dtoaresult; /* buffer allocated by dtoa */
#endif
u_long ulval; /* integer arguments %[diouxX] */
u_quad_t uqval; /* %q integers */
@@ -418,6 +419,9 @@
}
+#ifdef FLOATING_POINT
+ dtoaresult = NULL;
+#endif
FLOCKFILE(fp);
/* sorry, fprintf(read_only_file, "") returns EOF, not 0 */
if (cantwrite(fp)) {
@@ -608,8 +612,12 @@
break;
}
flags |= FPT;
+ if (dtoaresult != NULL) {
+ free(dtoaresult);
+ dtoaresult = NULL;
+ }
cp = cvt(_double, prec, flags, &softsign,
- &expt, ch, &ndig);
+ &expt, ch, &ndig, &dtoaresult);
if (ch == 'g' || ch == 'G') {
if (expt <= -4 || expt > prec)
ch = (ch == 'g') ? 'e' : 'E';
@@ -865,6 +873,10 @@
done:
FLUSH();
error:
+#ifdef FLOATING_POINT
+ if (dtoaresult != NULL)
+ free(dtoaresult);
+#endif
if (__sferror(fp))
ret = EOF;
FUNLOCKFILE(fp);
@@ -1203,13 +1215,14 @@
#ifdef FLOATING_POINT
-extern char *__dtoa __P((double, int, int, int *, int *, char **));
+extern char *__dtoa __P((double, int, int, int *, int *, char **, char **));
static char *
-cvt(value, ndigits, flags, sign, decpt, ch, length)
+cvt(value, ndigits, flags, sign, decpt, ch, length, dtoaresultp)
double value;
int ndigits, flags, *decpt, ch, *length;
char *sign;
+ char **dtoaresultp;
{
int mode, dsgn;
char *digits, *bp, *rve;
@@ -1231,7 +1244,8 @@
*sign = '-';
} else
*sign = '\000';
- digits = __dtoa(value, mode, ndigits, decpt, &dsgn, &rve);
+ digits = __dtoa(value, mode, ndigits, decpt, &dsgn, &rve,
+ dtoaresultp);
if ((ch != 'g' && ch != 'G') || flags & ALT) {
/* print trailing zeros */
bp = digits + ndigits;
- Tor Egge
This uncovered another old __dtoa() bug.
*resultp = (char *) malloc(i);
in the patch must be changed to
*resultp = (char *) malloc(i + 1);
in order to avoid writing beyond the end of the allocated memory.
Otherwise the sample program
#include <sys/types.h>
#include <stdio.h>
int
main(int argc,char **argv)
{
(void) malloc(4);
printf("%.23e\n", 2.4);
return 0;
}
linked with Electric Fence crashes on the i386 platform.
- Tor Egge
An updated patch that doesn't use spinlocks is enclosed.
- Tor Egge
Index: lib/libc/stdio/vfprintf.c
===================================================================
RCS file: /home/ncvs/src/lib/libc/stdio/vfprintf.c,v
retrieving revision 1.27
diff -u -r1.27 vfprintf.c
--- lib/libc/stdio/vfprintf.c 2001/02/10 06:25:33 1.27
+++ lib/libc/stdio/vfprintf.c 2001/02/10 22:29:23
@@ -268,7 +268,7 @@
#define BUF (MAXEXP+MAXFRACT+1) /* + decimal point */
#define DEFPREC 6
-static char *cvt __P((double, int, int, char *, int *, int, int *));
+static char *cvt __P((double, int, int, char *, int *, int, int *, char **));
static int exponent __P((char *, int, int));
#else /* no FLOATING_POINT */
@@ -315,6 +315,7 @@
int expsize; /* character count for expstr */
int ndig; /* actual number of digits returned by cvt */
char expstr[7]; /* buffer for exponent string */
+ char *dtoaresult; /* buffer allocated by dtoa */
#endif
u_long ulval; /* integer arguments %[diouxX] */
u_quad_t uqval; /* %q integers */
@@ -423,6 +424,9 @@
}
+#ifdef FLOATING_POINT
+ dtoaresult = NULL;
+#endif
/* sorry, fprintf(read_only_file, "") returns EOF, not 0 */
if (cantwrite(fp))
return (EOF);
@@ -608,8 +612,12 @@
break;
}
flags |= FPT;
+ if (dtoaresult != NULL) {
+ free(dtoaresult);
+ dtoaresult = NULL;
+ }
cp = cvt(_double, prec, flags, &softsign,
- &expt, ch, &ndig);
+ &expt, ch, &ndig, &dtoaresult);
if (ch == 'g' || ch == 'G') {
if (expt <= -4 || expt > prec)
ch = (ch == 'g') ? 'e' : 'E';
@@ -864,6 +872,10 @@
done:
FLUSH();
error:
+#ifdef FLOATING_POINT
+ if (dtoaresult != NULL)
+ free(dtoaresult);
+#endif
if (__sferror(fp))
ret = EOF;
if ((argtable != NULL) && (argtable != statargtable))
@@ -1195,11 +1207,11 @@
#ifdef FLOATING_POINT
-extern char *__dtoa __P((double, int, int, int *, int *, char **));
+extern char *__dtoa __P((double, int, int, int *, int *, char **, char **));
static char *
cvt(double value, int ndigits, int flags, char *sign, int *decpt,
- int ch, int *length)
+ int ch, int *length, char **dtoaresultp)
{
int mode, dsgn;
char *digits, *bp, *rve;
@@ -1221,7 +1233,8 @@
*sign = '-';
} else
*sign = '\000';
- digits = __dtoa(value, mode, ndigits, decpt, &dsgn, &rve);
+ digits = __dtoa(value, mode, ndigits, decpt, &dsgn, &rve,
+ dtoaresultp);
if ((ch != 'g' && ch != 'G') || flags & ALT) {
/* print trailing zeros */
bp = digits + ndigits;
Index: lib/libc/stdlib/netbsd_strtod.c
===================================================================
RCS file: /home/ncvs/src/lib/libc/stdlib/netbsd_strtod.c,v
retrieving revision 1.4
diff -u -r1.4 netbsd_strtod.c
--- lib/libc/stdlib/netbsd_strtod.c 2001/02/09 20:31:47 1.4
+++ lib/libc/stdlib/netbsd_strtod.c 2001/02/14 20:53:26
@@ -142,7 +142,7 @@
#include "memory.h"
#endif
#endif
-char *__dtoa __P((double, int, int, int *, int *, char **));
+char *__dtoa __P((double, int, int, int *, int *, char **, char **));
#ifdef MALLOC
#ifdef KR_headers
@@ -382,8 +382,6 @@
typedef struct Bigint Bigint;
- static Bigint *freelist[Kmax+1];
-
static Bigint *
Balloc
#ifdef KR_headers
@@ -395,15 +393,10 @@
int x;
Bigint *rv;
- if ((rv = freelist[k]) != NULL) {
- freelist[k] = rv->next;
- }
- else {
- x = 1 << k;
- rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(Long));
- rv->k = k;
- rv->maxwds = x;
- }
+ x = 1 << k;
+ rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(Long));
+ rv->k = k;
+ rv->maxwds = x;
rv->sign = rv->wds = 0;
return rv;
}
@@ -416,10 +409,7 @@
(Bigint *v)
#endif
{
- if (v) {
- v->next = freelist[v->k];
- freelist[v->k] = v;
- }
+ free(v);
}
#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \
@@ -1900,10 +1890,11 @@
char *
__dtoa
#ifdef KR_headers
- (d, mode, ndigits, decpt, sign, rve)
- double d; int mode, ndigits, *decpt, *sign; char **rve;
+ (d, mode, ndigits, decpt, sign, rve, resultp)
+ double d; int mode, ndigits, *decpt, *sign; char **rve, **resultp;
#else
- (double d, int mode, int ndigits, int *decpt, int *sign, char **rve)
+ (double d, int mode, int ndigits, int *decpt, int *sign, char **rve,
+ char **resultp)
#endif
{
/* Arguments ndigits, decpt, sign are similar to those
@@ -1953,15 +1944,6 @@
Bigint *mlo = NULL; /* pacify gcc */
double d2, ds, eps;
char *s, *s0;
- static Bigint *result;
- static int result_k;
-
- if (result) {
- result->k = result_k;
- result->maxwds = 1 << result_k;
- Bfree(result);
- result = 0;
- }
if (word0(d) & Sign_bit) {
/* set sign for everything, including 0's and NaNs */
@@ -2123,11 +2105,8 @@
if (i <= 0)
i = 1;
}
- j = sizeof(ULong);
- for(result_k = 0; sizeof(Bigint) - sizeof(ULong) + j <= i;
- j <<= 1) result_k++;
- result = Balloc(result_k);
- s = s0 = (char *)result;
+ *resultp = (char *) malloc(i + 1);
+ s = s0 = *resultp;
if (ilim >= 0 && ilim <= Quick_max && try_quick) {
Index: lib/libc/stdlib/strtod.c
===================================================================
RCS file: /home/ncvs/src/lib/libc/stdlib/strtod.c,v
retrieving revision 1.6
diff -u -r1.6 strtod.c
--- lib/libc/stdlib/strtod.c 2001/02/10 05:05:09 1.6
+++ lib/libc/stdlib/strtod.c 2001/02/14 20:53:26
@@ -372,8 +372,6 @@
typedef struct Bigint Bigint;
- static Bigint *freelist[Kmax+1];
-
static Bigint *
Balloc
#ifdef KR_headers
@@ -385,14 +383,10 @@
int x;
Bigint *rv;
- if ( (rv = freelist[k]) ) {
- freelist[k] = rv->next;
- } else {
- x = 1 << k;
- rv = (Bigint *)malloc(sizeof(Bigint) + (x-1)*sizeof(long));
- rv->k = k;
- rv->maxwds = x;
- }
+ x = 1 << k;
+ rv = (Bigint *)malloc(sizeof(Bigint) + (x-1)*sizeof(long));
+ rv->k = k;
+ rv->maxwds = x;
rv->sign = rv->wds = 0;
return rv;
}
@@ -405,10 +399,7 @@
(Bigint *v)
#endif
{
- if (v) {
- v->next = freelist[v->k];
- freelist[v->k] = v;
- }
+ free(v);
}
#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \
@@ -1844,10 +1835,11 @@
char *
__dtoa
#ifdef KR_headers
- (d, mode, ndigits, decpt, sign, rve)
- double d; int mode, ndigits, *decpt, *sign; char **rve;
+ (d, mode, ndigits, decpt, sign, rve, resultp)
+ double d; int mode, ndigits, *decpt, *sign; char **rve, **resultp;
#else
- (double d, int mode, int ndigits, int *decpt, int *sign, char **rve)
+ (double d, int mode, int ndigits, int *decpt, int *sign, char **rve,
+ char **resultp)
#endif
{
/* Arguments ndigits, decpt, sign are similar to those
@@ -1895,15 +1887,6 @@
Bigint *b, *b1, *delta, *mlo, *mhi, *S;
double d2, ds, eps;
char *s, *s0;
- static Bigint *result;
- static int result_k;
-
- if (result) {
- result->k = result_k;
- result->maxwds = 1 << result_k;
- Bfree(result);
- result = 0;
- }
if (word0(d) & Sign_bit) {
/* set sign for everything, including 0's and NaNs */
@@ -2062,11 +2045,8 @@
if (i <= 0)
i = 1;
}
- j = sizeof(unsigned long);
- for (result_k = 0; sizeof(Bigint) - sizeof(unsigned long) + j < i;
- j <<= 1) result_k++;
- result = Balloc(result_k);
- s = s0 = (char *)result;
+ *resultp = (char *) malloc(i + 1);
+ s = s0 = *resultp;
if (ilim >= 0 && ilim <= Quick_max && try_quick) {
On Wed, 14 Feb 2001 Tor.Egge@fast.no wrote: > > An updated patch that doesn't use spinlocks is enclosed. Looks OK to me. I was toying with the idea of adding unthreaded versions of _pthread_key_{create,delete}, _pthread_{get,set}specific to libc -- these would actually work for the single threaded case instead of being stub functions. -- Dan Eischen Responsible Changed From-To: freebsd-bugs->tegge Tor Egge is a committer. He seems to have a good idea what needs to be fixed. State Changed From-To: open->closed Fixed in lib/libc/stdio/vfprintf.c 1.28 and 1.22.2.2 lib/libc/stdlib/strtod.c 1.7 and 1.3.8.1 lib/libc/stdlib/netbsd_strtod.c 1.6 and 1.2.2.2 |
Printing floating point numbers in a threaded program might result in a segmentation fault or bus error. crash:~$ gdb ./threadbug4 GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-unknown-freebsd"... (gdb) run Starting program: /home/tegge/./threadbug4 Time is 2.664 Time is 5.544 Program received signal SIGSEGV, Segmentation fault. 0x80528c8 in bcopy () (gdb) where #0 0x80528c8 in bcopy () #1 0x4 in ?? () #2 0x8054473 in __dtoa () #3 0x80521d8 in vfprintf () #4 0x80508f2 in vfprintf () #5 0x804f9d1 in sprintf () #6 0x804819a in crashme (data=0x0) at threadbug4.c:27 #7 0x80487b0 in _thread_start () #8 0x0 in ?? () (gdb) How-To-Repeat: Compile and run the enclosed threaded program. ------------------------------------------- #include <sys/types.h> #include <sys/errno.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <assert.h> #include <signal.h> #include <pthread.h> #include <sys/time.h> #include <time.h> void *crashme(void *data) { char buf[200]; int i, j; double div; while (1) { i = random(); j = random(); if (j == 0) j = 1; div = (double) i / (double) j; sprintf(buf, "%6.5f", div); sprintf(buf, "%2.4f", div); sprintf(buf, "%3.6f", div); sprintf(buf, "%8.2f", div); sprintf(buf, "%10.2f", div); } } void reportloop(void) { struct timeval stime, now, delta; double fdelta; gettimeofday(&stime, NULL); while (1) { sleep(1); gettimeofday(&now, NULL); timersub(&now, &stime, &delta); fdelta = delta.tv_sec + ((double) delta.tv_usec) / 1000000.0; printf("Time is %6.3f\n", fdelta); fflush(stdout); } } int main(int argc,char **argv) { int i; pthread_t curthread; srandom(time(NULL)); for (i = 0; i < 10; i++) { pthread_create(&curthread, NULL, crashme, (void *) NULL); } reportloop(); exit(0); } -------------------------- crash:~$ cc -static -O -g -pthread -o threadbug4 threadbug4.c