# HG changeset patch # User Bruno Haible # Date 1552170640 -3600 # Node ID 02ed6264c100b9670ba5b59e890e319a69f2d16c # Parent 88b18d82fa610883f4687496b6a4a8457b1f513f strfmon_l: Fix -fsanitize=address finding. * lib/strfmon_l.c: Include , , , . (MAX_ARGS): Renamed from MAX_ARG_WORDS. (directive_t, directives_t): New types. (fmon_parse): New function. (rpl_strfmon_l): Don't call va_arg more often than needed for the format string. Consume 'long double' arguments in places where the format string indicates so. * modules/strfmon_l (Depends-on): Add 'stdbool'. diff -r 88b18d82fa61 -r 02ed6264c100 ChangeLog --- a/ChangeLog Sat Mar 09 22:21:25 2019 +0100 +++ b/ChangeLog Sat Mar 09 23:30:40 2019 +0100 @@ -1,3 +1,16 @@ +2019-03-09 Bruno Haible + + strfmon_l: Fix -fsanitize=address finding. + * lib/strfmon_l.c: Include , , , + . + (MAX_ARGS): Renamed from MAX_ARG_WORDS. + (directive_t, directives_t): New types. + (fmon_parse): New function. + (rpl_strfmon_l): Don't call va_arg more often than needed for the + format string. Consume 'long double' arguments in places where the + format string indicates so. + * modules/strfmon_l (Depends-on): Add 'stdbool'. + 2019-03-09 Bruno Haible crypto/des: Fix undefined behaviour. diff -r 88b18d82fa61 -r 02ed6264c100 lib/strfmon_l.c --- a/lib/strfmon_l.c Sat Mar 09 22:21:25 2019 +0100 +++ b/lib/strfmon_l.c Sat Mar 09 23:30:40 2019 +0100 @@ -19,13 +19,103 @@ /* Specification. */ #include +#include #include #include +#include +#include +#include #undef strfmon_l /* This override can only support a limited number of arguments. */ -#define MAX_ARG_WORDS 16 +#define MAX_ARGS 16 + +/* A parsed directive. */ +typedef struct +{ + bool needs_long_double; + const char *conversion_ptr; +} +directive_t; + +/* A parsed format string. */ +typedef struct +{ + size_t count; + directive_t dir[MAX_ARGS]; +} +directives_t; + +/* Parses a monetary format string. + Returns 0 and fills *DIRECTIVESP if valid. + Returns -1 if invalid. */ +static int +fmon_parse (const char *format, directives_t *directivesp) +{ + size_t count = 0; + const char *cp = format; + + while (*cp != '\0') + { + if (*cp++ == '%') + { + /* Parse flags. */ + while (*cp == '=' || *cp == '^' || *cp == '+' || *cp == '(' + || *cp == '!' || *cp == '-') + { + if (*cp == '=') + { + cp++; + if (*cp == '\0') + return -1; + } + cp++; + } + /* Parse field width. */ + while (*cp >= '0' && *cp <= '9') + cp++; + /* Parse left precision. */ + if (*cp == '#') + { + cp++; + while (*cp >= '0' && *cp <= '9') + cp++; + } + /* Parse right precision. */ + if (*cp == '.') + { + cp++; + while (*cp >= '0' && *cp <= '9') + cp++; + } + /* Now comes the conversion specifier. */ + if (*cp != '%') + { + if (count == MAX_ARGS) + /* Too many arguments. */ + return -1; + + /* glibc supports an 'L' modifier before the conversion specifier. */ + if (*cp == 'L') + { + cp++; + directivesp->dir[count].needs_long_double = true; + } + else + directivesp->dir[count].needs_long_double = false; + if (!(*cp == 'i' || *cp == 'n')) + return -1; + directivesp->dir[count].conversion_ptr = cp; + count++; + } + cp++; + } + } + + directivesp->count = count; + return 0; +} ssize_t rpl_strfmon_l (char *s, size_t maxsize, locale_t locale, const char *format, ...) @@ -33,9 +123,8 @@ /* Work around glibc 2.23 bug . */ va_list argptr; - double args[MAX_ARG_WORDS]; - int i; locale_t orig_locale; + directives_t directives; ssize_t result; orig_locale = uselocale ((locale_t)0); @@ -44,17 +133,101 @@ /* errno is set. */ return -1; - va_start (argptr, format); - /* Hack: Consume more arguments than those that are actually given. */ - for (i = 0; i < MAX_ARG_WORDS; i++) - args[i] = va_arg (argptr, double); + /* The format string may consume 'double' or 'long double' arguments. + In order not to have to link with libffcall or libffi, convert all + arguments to 'long double', and use a modified format string that + requests 'long double' arguments. But since 'long double' arguments + are only supported on glibc, do so only if the original format string + consumes at least one 'long double' argument. */ + if (fmon_parse (format, &directives) < 0) + { + errno = EINVAL; + result = -1; + } + else + { + bool use_long_double; + unsigned int i; + + use_long_double = false; + for (i = 0; i < directives.count; i++) + if (directives.dir[i].needs_long_double) + { + use_long_double = true; + break; + } + + va_start (argptr, format); + + if (use_long_double) + { + char *ld_format; + + /* Allocate room for the modified format string. */ + ld_format = (char *) malloc (strlen (format) + directives.count + 1); + if (ld_format == NULL) + { + errno = ENOMEM; + result = -1; + } + else + { + long double args[MAX_ARGS]; - result = strfmon_l (s, maxsize, locale, format, - args[0], args[1], args[2], args[3], args[4], args[5], - args[6], args[7], args[8], args[9], args[10], args[11], - args[12], args[13], args[14], args[15]); + /* Create the modified format string. */ + { + const char *p = format; + char *dest = ld_format; + for (i = 0; i < directives.count; i++) + { + const char *q = directives.dir[i].conversion_ptr; + memcpy (dest, p, q - p); + dest += q - p; + if (!directives.dir[i].needs_long_double) + *dest++ = 'L'; + p = q; + } + strcpy (dest, p); + } + + /* Set up arguments array. */ + for (i = 0; i < directives.count; i++) + args[i] = (directives.dir[i].needs_long_double + ? va_arg (argptr, long double) + : (long double) va_arg (argptr, double)); + /* Avoid uninitialized memory references. */ + for (; i < MAX_ARGS; i++) + args[i] = 0.0L; - va_end (argptr); + result = strfmon_l (s, maxsize, locale, ld_format, + args[0], args[1], args[2], args[3], args[4], + args[5], args[6], args[7], args[8], args[9], + args[10], args[11], args[12], args[13], + args[14], args[15]); + + free (ld_format); + } + } + else + { + double args[MAX_ARGS]; + + /* Set up arguments array. */ + for (i = 0; i < directives.count; i++) + args[i] = va_arg (argptr, double); + /* Avoid uninitialized memory references. */ + for (; i < MAX_ARGS; i++) + args[i] = 0.0; + + result = strfmon_l (s, maxsize, locale, format, + args[0], args[1], args[2], args[3], args[4], + args[5], args[6], args[7], args[8], args[9], + args[10], args[11], args[12], args[13], args[14], + args[15]); + } + + va_end (argptr); + } if (uselocale (orig_locale) == (locale_t)0) /* errno is set. */ diff -r 88b18d82fa61 -r 02ed6264c100 modules/strfmon_l --- a/modules/strfmon_l Sat Mar 09 22:21:25 2019 +0100 +++ b/modules/strfmon_l Sat Mar 09 23:30:40 2019 +0100 @@ -8,6 +8,7 @@ Depends-on: monetary extensions +stdbool [test $REPLACE_STRFMON_L = 1] configure.ac: gl_FUNC_STRFMON_L