Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a4930e7

Browse files
committed
Fix PGLC_localeconv() to handle errors better.
The code was intentionally not very careful about leaking strdup'd strings in case of an error. That was forgivable probably, but it also failed to notice strdup() failures, which could lead to subsequent null-pointer-dereference crashes, since many callers unsurprisingly didn't check for null pointers in the struct lconv fields. An even worse problem is that it could throw error while we were setlocale'd to a non-C locale, causing unwanted behavior in subsequent libc calls. Rewrite to ensure that we cannot throw elog(ERROR) until after we've restored the previous locale settings, or at least attempted to. (I'm sorely tempted to make restore failure be a FATAL error, but will refrain for the moment.) Having done that, it's not much more work to ensure that we clean up strdup'd storage on the way out, too. This code is substantially the same in all supported branches, so back-patch all the way. Michael Paquier and Tom Lane Discussion: <CAB7nPqRMbGqa_mesopcn4MPyTs34eqtVEK7ELYxvvV=oqS00YA@mail.gmail.com>
1 parent 4324ade commit a4930e7

File tree

1 file changed

+164
-52
lines changed

1 file changed

+164
-52
lines changed

src/backend/utils/adt/pg_locale.c

Lines changed: 164 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -382,51 +382,91 @@ assign_locale_messages(const char *newval, void *extra)
382382

383383
/*
384384
* Frees the malloced content of a struct lconv. (But not the struct
385-
* itself.)
385+
* itself.) It's important that this not throw elog(ERROR).
386386
*/
387387
static void
388388
free_struct_lconv(struct lconv * s)
389389
{
390-
if (s->currency_symbol)
391-
free(s->currency_symbol);
392390
if (s->decimal_point)
393391
free(s->decimal_point);
394-
if (s->grouping)
395-
free(s->grouping);
396392
if (s->thousands_sep)
397393
free(s->thousands_sep);
394+
if (s->grouping)
395+
free(s->grouping);
398396
if (s->int_curr_symbol)
399397
free(s->int_curr_symbol);
398+
if (s->currency_symbol)
399+
free(s->currency_symbol);
400400
if (s->mon_decimal_point)
401401
free(s->mon_decimal_point);
402-
if (s->mon_grouping)
403-
free(s->mon_grouping);
404402
if (s->mon_thousands_sep)
405403
free(s->mon_thousands_sep);
406-
if (s->negative_sign)
407-
free(s->negative_sign);
404+
if (s->mon_grouping)
405+
free(s->mon_grouping);
408406
if (s->positive_sign)
409407
free(s->positive_sign);
408+
if (s->negative_sign)
409+
free(s->negative_sign);
410+
}
411+
412+
/*
413+
* Check that all fields of a struct lconv (or at least, the ones we care
414+
* about) are non-NULL. The field list must match free_struct_lconv().
415+
*/
416+
static bool
417+
struct_lconv_is_valid(struct lconv * s)
418+
{
419+
if (s->decimal_point == NULL)
420+
return false;
421+
if (s->thousands_sep == NULL)
422+
return false;
423+
if (s->grouping == NULL)
424+
return false;
425+
if (s->int_curr_symbol == NULL)
426+
return false;
427+
if (s->currency_symbol == NULL)
428+
return false;
429+
if (s->mon_decimal_point == NULL)
430+
return false;
431+
if (s->mon_thousands_sep == NULL)
432+
return false;
433+
if (s->mon_grouping == NULL)
434+
return false;
435+
if (s->positive_sign == NULL)
436+
return false;
437+
if (s->negative_sign == NULL)
438+
return false;
439+
return true;
410440
}
411441

412442

413443
/*
414-
* Return a strdup'ed string converted from the specified encoding to the
444+
* Convert the strdup'd string at *str from the specified encoding to the
415445
* database encoding.
416446
*/
417-
static char *
418-
db_encoding_strdup(int encoding, const char *str)
447+
static void
448+
db_encoding_convert(int encoding, char **str)
419449
{
420450
char *pstr;
421451
char *mstr;
422452

423453
/* convert the string to the database encoding */
424-
pstr = pg_any_to_server(str, strlen(str), encoding);
454+
pstr = pg_any_to_server(*str, strlen(*str), encoding);
455+
if (pstr == *str)
456+
return; /* no conversion happened */
457+
458+
/* need it malloc'd not palloc'd */
425459
mstr = strdup(pstr);
426-
if (pstr != str)
427-
pfree(pstr);
460+
if (mstr == NULL)
461+
ereport(ERROR,
462+
(errcode(ERRCODE_OUT_OF_MEMORY),
463+
errmsg("out of memory")));
464+
465+
/* replace old string */
466+
free(*str);
467+
*str = mstr;
428468

429-
return mstr;
469+
pfree(pstr);
430470
}
431471

432472

@@ -440,13 +480,10 @@ PGLC_localeconv(void)
440480
static struct lconv CurrentLocaleConv;
441481
static bool CurrentLocaleConvAllocated = false;
442482
struct lconv *extlconv;
483+
struct lconv worklconv;
484+
bool trouble = false;
443485
char *save_lc_monetary;
444486
char *save_lc_numeric;
445-
char *decimal_point;
446-
char *grouping;
447-
char *thousands_sep;
448-
int encoding;
449-
450487
#ifdef WIN32
451488
char *save_lc_ctype;
452489
#endif
@@ -462,6 +499,20 @@ PGLC_localeconv(void)
462499
CurrentLocaleConvAllocated = false;
463500
}
464501

502+
/*
503+
* This is tricky because we really don't want to risk throwing error
504+
* while the locale is set to other than our usual settings. Therefore,
505+
* the process is: collect the usual settings, set locale to special
506+
* setting, copy relevant data into worklconv using strdup(), restore
507+
* normal settings, convert data to desired encoding, and finally stash
508+
* the collected data in CurrentLocaleConv. This makes it safe if we
509+
* throw an error during encoding conversion or run out of memory anywhere
510+
* in the process. All data pointed to by struct lconv members is
511+
* allocated with strdup, to avoid premature elog(ERROR) and to allow
512+
* using a single cleanup routine.
513+
*/
514+
memset(&worklconv, 0, sizeof(worklconv));
515+
465516
/* Save user's values of monetary and numeric locales */
466517
save_lc_monetary = setlocale(LC_MONETARY, NULL);
467518
if (save_lc_monetary)
@@ -477,7 +528,7 @@ PGLC_localeconv(void)
477528
* Ideally, monetary and numeric local symbols could be returned in any
478529
* server encoding. Unfortunately, the WIN32 API does not allow
479530
* setlocale() to return values in a codepage/CTYPE that uses more than
480-
* two bytes per character, like UTF-8:
531+
* two bytes per character, such as UTF-8:
481532
*
482533
* http://msdn.microsoft.com/en-us/library/x99tb11d.aspx
483534
*
@@ -487,7 +538,7 @@ PGLC_localeconv(void)
487538
*
488539
* Therefore, we set LC_CTYPE to match LC_NUMERIC or LC_MONETARY (which
489540
* cannot be UTF8), call localeconv(), and then convert from the
490-
* numeric/monitary LC_CTYPE to the server encoding. One example use of
541+
* numeric/monetary LC_CTYPE to the server encoding. One example use of
491542
* this is for the Euro symbol.
492543
*
493544
* Perhaps someday we will use GetLocaleInfoW() which returns values in
@@ -499,18 +550,20 @@ PGLC_localeconv(void)
499550
if (save_lc_ctype)
500551
save_lc_ctype = pstrdup(save_lc_ctype);
501552

553+
/* Here begins the critical section where we must not throw error */
554+
502555
/* use numeric to set the ctype */
503556
setlocale(LC_CTYPE, locale_numeric);
504557
#endif
505558

506559
/* Get formatting information for numeric */
507560
setlocale(LC_NUMERIC, locale_numeric);
508561
extlconv = localeconv();
509-
encoding = pg_get_encoding_from_locale(locale_numeric, true);
510562

511-
decimal_point = db_encoding_strdup(encoding, extlconv->decimal_point);
512-
thousands_sep = db_encoding_strdup(encoding, extlconv->thousands_sep);
513-
grouping = strdup(extlconv->grouping);
563+
/* Must copy data now in case setlocale() overwrites it */
564+
worklconv.decimal_point = strdup(extlconv->decimal_point);
565+
worklconv.thousands_sep = strdup(extlconv->thousands_sep);
566+
worklconv.grouping = strdup(extlconv->grouping);
514567

515568
#ifdef WIN32
516569
/* use monetary to set the ctype */
@@ -520,52 +573,111 @@ PGLC_localeconv(void)
520573
/* Get formatting information for monetary */
521574
setlocale(LC_MONETARY, locale_monetary);
522575
extlconv = localeconv();
523-
encoding = pg_get_encoding_from_locale(locale_monetary, true);
524576

525-
/*
526-
* Must copy all values since restoring internal settings may overwrite
527-
* localeconv()'s results. Note that if we were to fail within this
528-
* sequence before reaching "CurrentLocaleConvAllocated = true", we could
529-
* leak some memory --- but not much, so it's not worth agonizing over.
530-
*/
531-
CurrentLocaleConv = *extlconv;
532-
CurrentLocaleConv.decimal_point = decimal_point;
533-
CurrentLocaleConv.grouping = grouping;
534-
CurrentLocaleConv.thousands_sep = thousands_sep;
535-
CurrentLocaleConv.int_curr_symbol = db_encoding_strdup(encoding, extlconv->int_curr_symbol);
536-
CurrentLocaleConv.currency_symbol = db_encoding_strdup(encoding, extlconv->currency_symbol);
537-
CurrentLocaleConv.mon_decimal_point = db_encoding_strdup(encoding, extlconv->mon_decimal_point);
538-
CurrentLocaleConv.mon_grouping = strdup(extlconv->mon_grouping);
539-
CurrentLocaleConv.mon_thousands_sep = db_encoding_strdup(encoding, extlconv->mon_thousands_sep);
540-
CurrentLocaleConv.negative_sign = db_encoding_strdup(encoding, extlconv->negative_sign);
541-
CurrentLocaleConv.positive_sign = db_encoding_strdup(encoding, extlconv->positive_sign);
542-
CurrentLocaleConvAllocated = true;
577+
/* Must copy data now in case setlocale() overwrites it */
578+
worklconv.int_curr_symbol = strdup(extlconv->int_curr_symbol);
579+
worklconv.currency_symbol = strdup(extlconv->currency_symbol);
580+
worklconv.mon_decimal_point = strdup(extlconv->mon_decimal_point);
581+
worklconv.mon_thousands_sep = strdup(extlconv->mon_thousands_sep);
582+
worklconv.mon_grouping = strdup(extlconv->mon_grouping);
583+
worklconv.positive_sign = strdup(extlconv->positive_sign);
584+
worklconv.negative_sign = strdup(extlconv->negative_sign);
585+
/* Copy scalar fields as well */
586+
worklconv.int_frac_digits = extlconv->int_frac_digits;
587+
worklconv.frac_digits = extlconv->frac_digits;
588+
worklconv.p_cs_precedes = extlconv->p_cs_precedes;
589+
worklconv.p_sep_by_space = extlconv->p_sep_by_space;
590+
worklconv.n_cs_precedes = extlconv->n_cs_precedes;
591+
worklconv.n_sep_by_space = extlconv->n_sep_by_space;
592+
worklconv.p_sign_posn = extlconv->p_sign_posn;
593+
worklconv.n_sign_posn = extlconv->n_sign_posn;
543594

544595
/* Try to restore internal settings */
545596
if (save_lc_monetary)
546597
{
547598
if (!setlocale(LC_MONETARY, save_lc_monetary))
548-
elog(WARNING, "failed to restore old locale");
549-
pfree(save_lc_monetary);
599+
trouble = true;
550600
}
551601

552602
if (save_lc_numeric)
553603
{
554604
if (!setlocale(LC_NUMERIC, save_lc_numeric))
555-
elog(WARNING, "failed to restore old locale");
556-
pfree(save_lc_numeric);
605+
trouble = true;
557606
}
558607

559608
#ifdef WIN32
560609
/* Try to restore internal ctype settings */
561610
if (save_lc_ctype)
562611
{
563612
if (!setlocale(LC_CTYPE, save_lc_ctype))
564-
elog(WARNING, "failed to restore old locale");
565-
pfree(save_lc_ctype);
613+
trouble = true;
566614
}
567615
#endif
568616

617+
/*
618+
* At this point we've done our best to clean up, and can call functions
619+
* that might possibly throw errors with a clean conscience. But let's
620+
* make sure we don't leak any already-strdup'd fields in worklconv.
621+
*/
622+
PG_TRY();
623+
{
624+
int encoding;
625+
626+
/*
627+
* Report it if we failed to restore anything. Perhaps this should be
628+
* FATAL, rather than continuing with bad locale settings?
629+
*/
630+
if (trouble)
631+
elog(WARNING, "failed to restore old locale");
632+
633+
/* Release the pstrdup'd locale names */
634+
if (save_lc_monetary)
635+
pfree(save_lc_monetary);
636+
if (save_lc_numeric)
637+
pfree(save_lc_numeric);
638+
#ifdef WIN32
639+
if (save_lc_ctype)
640+
pfree(save_lc_ctype);
641+
#endif
642+
643+
/* If any of the preceding strdup calls failed, complain now. */
644+
if (!struct_lconv_is_valid(&worklconv))
645+
ereport(ERROR,
646+
(errcode(ERRCODE_OUT_OF_MEMORY),
647+
errmsg("out of memory")));
648+
649+
/*
650+
* Now we must perform encoding conversion from whatever's associated
651+
* with the locale into the database encoding.
652+
*/
653+
encoding = pg_get_encoding_from_locale(locale_numeric, true);
654+
655+
db_encoding_convert(encoding, &worklconv.decimal_point);
656+
db_encoding_convert(encoding, &worklconv.thousands_sep);
657+
/* grouping is not text and does not require conversion */
658+
659+
encoding = pg_get_encoding_from_locale(locale_monetary, true);
660+
661+
db_encoding_convert(encoding, &worklconv.int_curr_symbol);
662+
db_encoding_convert(encoding, &worklconv.currency_symbol);
663+
db_encoding_convert(encoding, &worklconv.mon_decimal_point);
664+
db_encoding_convert(encoding, &worklconv.mon_thousands_sep);
665+
/* mon_grouping is not text and does not require conversion */
666+
db_encoding_convert(encoding, &worklconv.positive_sign);
667+
db_encoding_convert(encoding, &worklconv.negative_sign);
668+
}
669+
PG_CATCH();
670+
{
671+
free_struct_lconv(&worklconv);
672+
PG_RE_THROW();
673+
}
674+
PG_END_TRY();
675+
676+
/*
677+
* Everything is good, so save the results.
678+
*/
679+
CurrentLocaleConv = worklconv;
680+
CurrentLocaleConvAllocated = true;
569681
CurrentLocaleConvValid = true;
570682
return &CurrentLocaleConv;
571683
}

0 commit comments

Comments
 (0)