From: Bruno Haible Date: Mon, 17 Nov 2003 15:14:21 +0000 (+0000) Subject: Use xsize.h to protect against memory size overflows. X-Git-Tag: cvs-readonly~4314 X-Git-Url: http://erislabs.net/gitweb/?a=commitdiff_plain;h=a416aa560a13c4fc49a39ab5f0225c999bcda510;p=gnulib.git Use xsize.h to protect against memory size overflows. --- diff --git a/ChangeLog b/ChangeLog index b733dd0fb..6f6f7fc14 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2003-11-17 Bruno Haible + + * modules/vasnprintf (Files): Add m4/ssize_t.m4. + (Depends-on): Add xsize. + 2003-11-12 Paul Eggert * modules/xalloc (Files): Undo latest change, since xalloc.h diff --git a/lib/ChangeLog b/lib/ChangeLog index 85e3ced1d..7abf20d52 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,38 @@ +2003-11-16 Paul Eggert + Bruno Haible + + Protect against address arithmetic overflow. + * printf-args.h: Include stddef.h. + (arguments): Change type of field 'count' to size_t. + * printf-args.c (printf_fetchargs): Use size_t instead of + 'unsigned int' where appropriate. + * printf-parse.h: Include sys/types.h. + (char_directive): Change type of *arg_index fields to ssize_t. + (char_directives): Change type of fields 'count', max_*_length to + size_t. + * printf-parse.c: Include sys/types.h and xsize.h. + (SSIZE_MAX): Define fallback value. + (PRINTF_PARSE): Use size_t instead of 'unsigned int' and ssize_t + instead of 'int' where appropriate. Check a_allocated, d_allocated + against overflow. Reject %m$ argument numbers > SSIZE_MAX + 1. + * vasnprintf.c: Include xsize.h. + (VASNPRINTF): Use size_t instead of 'unsigned int' where appropriate. + Check alloca, malloc, realloc, ENSURE_ALLOCATION arguments against + overflow. Avoid wraparound when converting a width or precision from + decimal to binary. + +2003-11-16 Bruno Haible + + Update from GNU gettext. + * printf-parse.c: Generalize to it can be compiled for wide strings. + (PRINTF_PARSE, CHAR_T, DIRECTIVE, DIRECTIVES): New macros. + * vasnprintf.c: Generalize to it can be compiled for wide strings. + (VASNPRINTF, CHAR_T, DIRECTIVE, DIRECTIVES, PRINTF_PARSE, USE_SNPRINTF, + SNPRINTF): New macros. + Don't include if the file is used inside libintl. + (local_wcslen): New function, for Solaris 2.5.1. + (VASNPRINTF): Use it instead of wcslen. + 2003-11-16 Bruno Haible * xsize.h (xmax): New function. diff --git a/lib/printf-args.c b/lib/printf-args.c index 6972cfd42..926ae8ffe 100644 --- a/lib/printf-args.c +++ b/lib/printf-args.c @@ -28,7 +28,7 @@ STATIC int printf_fetchargs (va_list args, arguments *a) { - unsigned int i; + size_t i; argument *ap; for (i = 0, ap = &a->arg[0]; i < a->count; i++, ap++) diff --git a/lib/printf-args.h b/lib/printf-args.h index c938192b7..4b75c723f 100644 --- a/lib/printf-args.h +++ b/lib/printf-args.h @@ -18,6 +18,9 @@ #ifndef _PRINTF_ARGS_H #define _PRINTF_ARGS_H +/* Get size_t. */ +#include + /* Get wchar_t. */ #ifdef HAVE_WCHAR_T # include @@ -116,7 +119,7 @@ argument; typedef struct { - unsigned int count; + size_t count; argument *arg; } arguments; diff --git a/lib/printf-parse.c b/lib/printf-parse.c index ad42e42c5..fceb5c97c 100644 --- a/lib/printf-parse.c +++ b/lib/printf-parse.c @@ -29,6 +29,9 @@ /* Get size_t, NULL. */ #include +/* Get ssize_t. */ +#include + /* Get intmax_t. */ #if HAVE_STDINT_H_WITH_UINTMAX # include @@ -40,6 +43,13 @@ /* malloc(), realloc(), free(). */ #include +/* Checked size_t computations. */ +#include "xsize.h" + +#ifndef SSIZE_MAX +# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) +#endif + #if WIDE_CHAR_VERSION # define PRINTF_PARSE wprintf_parse # define CHAR_T wchar_t @@ -59,11 +69,11 @@ int PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) { const CHAR_T *cp = format; /* pointer into format */ - int arg_posn = 0; /* number of regular arguments consumed */ - unsigned int d_allocated; /* allocated elements of d->dir */ - unsigned int a_allocated; /* allocated elements of a->arg */ - unsigned int max_width_length = 0; - unsigned int max_precision_length = 0; + ssize_t arg_posn = 0; /* number of regular arguments consumed */ + size_t d_allocated; /* allocated elements of d->dir */ + size_t a_allocated; /* allocated elements of a->arg */ + size_t max_width_length = 0; + size_t max_precision_length = 0; d->count = 0; d_allocated = 1; @@ -78,16 +88,22 @@ PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) #define REGISTER_ARG(_index_,_type_) \ { \ - unsigned int n = (_index_); \ + size_t n = (_index_); \ if (n >= a_allocated) \ { \ + size_t memory_size; \ argument *memory; \ - a_allocated = 2 * a_allocated; \ + \ + a_allocated = xtimes (a_allocated, 2); \ if (a_allocated <= n) \ - a_allocated = n + 1; \ + a_allocated = xsum (n, 1); \ + memory_size = xtimes (a_allocated, sizeof (argument)); \ + if (size_overflow_p (memory_size)) \ + /* Overflow, would lead to out of memory. */ \ + goto error; \ memory = (a->arg \ - ? realloc (a->arg, a_allocated * sizeof (argument)) \ - : malloc (a_allocated * sizeof (argument))); \ + ? realloc (a->arg, memory_size) \ + : malloc (memory_size)); \ if (memory == NULL) \ /* Out of memory. */ \ goto error; \ @@ -107,7 +123,7 @@ PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) CHAR_T c = *cp++; if (c == '%') { - int arg_index = -1; + ssize_t arg_index = -1; DIRECTIVE *dp = &d->dir[d->count];/* pointer to next directive */ /* Initialize the next directive. */ @@ -130,13 +146,16 @@ PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) ; if (*np == '$') { - unsigned int n = 0; + size_t n = 0; for (np = cp; *np >= '0' && *np <= '9'; np++) - n = 10 * n + (*np - '0'); + n = xsum (xtimes (n, 10), *np - '0'); if (n == 0) /* Positional argument 0. */ goto error; + if (size_overflow_p (n) || n - 1 > SSIZE_MAX) + /* n too large, would lead to out of memory later. */ + goto error; arg_index = n - 1; cp = np + 1; } @@ -197,24 +216,32 @@ PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) ; if (*np == '$') { - unsigned int n = 0; + size_t n = 0; for (np = cp; *np >= '0' && *np <= '9'; np++) - n = 10 * n + (*np - '0'); + n = xsum (xtimes (n, 10), *np - '0'); if (n == 0) /* Positional argument 0. */ goto error; + if (size_overflow_p (n) || n - 1 > SSIZE_MAX) + /* n too large, would lead to out of memory later. */ + goto error; dp->width_arg_index = n - 1; cp = np + 1; } } if (dp->width_arg_index < 0) - dp->width_arg_index = arg_posn++; + { + dp->width_arg_index = arg_posn++; + if (dp->width_arg_index < 0) + /* arg_posn wrapped around at SSIZE_MAX. */ + goto error; + } REGISTER_ARG (dp->width_arg_index, TYPE_INT); } else if (*cp >= '0' && *cp <= '9') { - unsigned int width_length; + size_t width_length; dp->width_start = cp; for (; *cp >= '0' && *cp <= '9'; cp++) @@ -246,24 +273,33 @@ PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) ; if (*np == '$') { - unsigned int n = 0; + size_t n = 0; for (np = cp; *np >= '0' && *np <= '9'; np++) - n = 10 * n + (*np - '0'); + n = xsum (xtimes (n, 10), *np - '0'); if (n == 0) /* Positional argument 0. */ goto error; + if (size_overflow_p (n) || n - 1 > SSIZE_MAX) + /* n too large, would lead to out of memory + later. */ + goto error; dp->precision_arg_index = n - 1; cp = np + 1; } } if (dp->precision_arg_index < 0) - dp->precision_arg_index = arg_posn++; + { + dp->precision_arg_index = arg_posn++; + if (dp->precision_arg_index < 0) + /* arg_posn wrapped around at SSIZE_MAX. */ + goto error; + } REGISTER_ARG (dp->precision_arg_index, TYPE_INT); } else { - unsigned int precision_length; + size_t precision_length; dp->precision_start = cp - 1; for (; *cp >= '0' && *cp <= '9'; cp++) @@ -456,7 +492,12 @@ PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) { dp->arg_index = arg_index; if (dp->arg_index < 0) - dp->arg_index = arg_posn++; + { + dp->arg_index = arg_posn++; + if (dp->arg_index < 0) + /* arg_posn wrapped around at SSIZE_MAX. */ + goto error; + } REGISTER_ARG (dp->arg_index, type); } dp->conversion = c; @@ -466,10 +507,18 @@ PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a) d->count++; if (d->count >= d_allocated) { + size_t memory_size; DIRECTIVE *memory; - d_allocated = 2 * d_allocated; - memory = realloc (d->dir, d_allocated * sizeof (DIRECTIVE)); + d_allocated = xtimes (d_allocated, 2); + if (size_overflow_p (d_allocated)) + /* Overflow, would lead to out of memory. */ + goto error; + memory_size = xtimes (d_allocated, sizeof (DIRECTIVE)); + if (size_overflow_p (memory_size)) + /* Overflow, would lead to out of memory. */ + goto error; + memory = realloc (d->dir, memory_size); if (memory == NULL) /* Out of memory. */ goto error; diff --git a/lib/printf-parse.h b/lib/printf-parse.h index 54b429f12..56f7cd280 100644 --- a/lib/printf-parse.h +++ b/lib/printf-parse.h @@ -1,5 +1,5 @@ /* Parse printf format string. - Copyright (C) 1999, 2002 Free Software Foundation, Inc. + Copyright (C) 1999, 2002-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,9 @@ #include "printf-args.h" +/* Get ssize_t. */ +#include + /* Flags */ #define FLAG_GROUP 1 /* ' flag */ @@ -37,22 +40,22 @@ typedef struct int flags; const char* width_start; const char* width_end; - int width_arg_index; + ssize_t width_arg_index; const char* precision_start; const char* precision_end; - int precision_arg_index; + ssize_t precision_arg_index; char conversion; /* d i o u x X f e E g G c s p n U % but not C S */ - int arg_index; + ssize_t arg_index; } char_directive; /* A parsed format string. */ typedef struct { - unsigned int count; + size_t count; char_directive *dir; - unsigned int max_width_length; - unsigned int max_precision_length; + size_t max_width_length; + size_t max_precision_length; } char_directives; diff --git a/lib/vasnprintf.c b/lib/vasnprintf.c index 15ba9a130..380170dee 100644 --- a/lib/vasnprintf.c +++ b/lib/vasnprintf.c @@ -48,6 +48,9 @@ # include "printf-parse.h" #endif +/* Checked size_t computations. */ +#include "xsize.h" + /* For those losing systems which don't have 'alloca' we have to add some additional code emulating it. */ #ifdef HAVE_ALLOCA @@ -136,11 +139,12 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar } { + size_t buf_neededlength = + xsum4 (7, d.max_width_length, d.max_precision_length, 6); CHAR_T *buf = - (CHAR_T *) alloca ((7 + d.max_width_length + d.max_precision_length + 6) - * sizeof (CHAR_T)); + (CHAR_T *) alloca (xtimes (buf_neededlength, sizeof (CHAR_T))); const CHAR_T *cp; - unsigned int i; + size_t i; DIRECTIVE *dp; /* Output string accumulator. */ CHAR_T *result; @@ -162,28 +166,26 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar result is either == resultbuf or == NULL or malloc-allocated. If length > 0, then result != NULL. */ + /* Ensures that allocated >= needed. Aborts through a jump to + out_of_memory if needed is SIZE_MAX or otherwise too big. */ #define ENSURE_ALLOCATION(needed) \ if ((needed) > allocated) \ { \ + size_t memory_size; \ CHAR_T *memory; \ \ - allocated = (allocated > 0 ? 2 * allocated : 12); \ + allocated = (allocated > 0 ? xtimes (allocated, 2) : 12); \ if ((needed) > allocated) \ allocated = (needed); \ + memory_size = xtimes (allocated, sizeof (CHAR_T)); \ + if (size_overflow_p (memory_size)) \ + goto out_of_memory; \ if (result == resultbuf || result == NULL) \ - memory = (CHAR_T *) malloc (allocated * sizeof (CHAR_T)); \ + memory = (CHAR_T *) malloc (memory_size); \ else \ - memory = (CHAR_T *) realloc (result, allocated * sizeof (CHAR_T)); \ - \ + memory = (CHAR_T *) realloc (result, memory_size); \ if (memory == NULL) \ - { \ - if (!(result == resultbuf || result == NULL)) \ - free (result); \ - freea (buf); \ - CLEANUP (); \ - errno = ENOMEM; \ - return NULL; \ - } \ + goto out_of_memory; \ if (result == resultbuf && length > 0) \ memcpy (memory, result, length * sizeof (CHAR_T)); \ result = memory; \ @@ -194,10 +196,11 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar if (cp != dp->dir_start) { size_t n = dp->dir_start - cp; + size_t augmented_length = xsum (length, n); - ENSURE_ALLOCATION (length + n); + ENSURE_ALLOCATION (augmented_length); memcpy (result + length, cp, n * sizeof (CHAR_T)); - length += n; + length = augmented_length; } if (i == d.count) break; @@ -205,11 +208,14 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar /* Execute a single directive. */ if (dp->conversion == '%') { + size_t augmented_length; + if (!(dp->arg_index < 0)) abort (); - ENSURE_ALLOCATION (length + 1); + augmented_length = xsum (length, 1); + ENSURE_ALLOCATION (augmented_length); result[length] = '%'; - length += 1; + length = augmented_length; } else { @@ -248,15 +254,15 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar unsigned int prefix_count; int prefixes[2]; #if !USE_SNPRINTF - unsigned int tmp_length; + size_t tmp_length; CHAR_T tmpbuf[700]; CHAR_T *tmp; /* Allocate a temporary buffer of sufficient size for calling sprintf. */ { - unsigned int width; - unsigned int precision; + size_t width; + size_t precision; width = 0; if (dp->width_start != dp->width_end) @@ -268,14 +274,14 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar if (!(a.arg[dp->width_arg_index].type == TYPE_INT)) abort (); arg = a.arg[dp->width_arg_index].a.a_int; - width = (arg < 0 ? -arg : arg); + width = (arg < 0 ? (unsigned int) (-arg) : arg); } else { const CHAR_T *digitp = dp->width_start; do - width = width * 10 + (*digitp++ - '0'); + width = xsum (xtimes (width, 10), *digitp++ - '0'); while (digitp != dp->width_end); } } @@ -298,7 +304,7 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar precision = 0; do - precision = precision * 10 + (*digitp++ - '0'); + precision = xsum (xtimes (precision, 10), *digitp++ - '0'); while (digitp != dp->precision_end); } } @@ -399,7 +405,6 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar * 2 /* estimate for FLAG_GROUP */ ) + 1 /* turn floor into ceil */ - + precision + 10; /* sign, decimal point etc. */ else # endif @@ -409,15 +414,15 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar * 2 /* estimate for FLAG_GROUP */ ) + 1 /* turn floor into ceil */ - + precision + 10; /* sign, decimal point etc. */ + tmp_length = xsum (tmp_length, precision); break; case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': tmp_length = - precision - + 12; /* sign, decimal point, exponent etc. */ + 12; /* sign, decimal point, exponent etc. */ + tmp_length = xsum (tmp_length, precision); break; case 'c': @@ -432,14 +437,14 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar case 's': # ifdef HAVE_WCHAR_T if (type == TYPE_WIDE_STRING) -# if WIDE_CHAR_VERSION - tmp_length = - local_wcslen (a.arg[dp->arg_index].a.a_wide_string); -# else - tmp_length = - local_wcslen (a.arg[dp->arg_index].a.a_wide_string) - * MB_CUR_MAX; + { + tmp_length = + local_wcslen (a.arg[dp->arg_index].a.a_wide_string); + +# if !WIDE_CHAR_VERSION + tmp_length = xtimes (tmp_length, MB_CUR_MAX); # endif + } else # endif tmp_length = strlen (a.arg[dp->arg_index].a.a_string); @@ -461,24 +466,22 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar if (tmp_length < width) tmp_length = width; - tmp_length++; /* account for trailing NUL */ + tmp_length = xsum (tmp_length, 1); /* account for trailing NUL */ } if (tmp_length <= sizeof (tmpbuf) / sizeof (CHAR_T)) tmp = tmpbuf; else { - tmp = (CHAR_T *) malloc (tmp_length * sizeof (CHAR_T)); + size_t tmp_memsize = xtimes (tmp_length, sizeof (CHAR_T)); + + if (size_overflow_p (tmp_memsize)) + /* Overflow, would lead to out of memory. */ + goto out_of_memory; + tmp = (CHAR_T *) malloc (tmp_memsize); if (tmp == NULL) - { - /* Out of memory. */ - if (!(result == resultbuf || result == NULL)) - free (result); - freea (buf); - CLEANUP (); - errno = ENOMEM; - return NULL; - } + /* Out of memory. */ + goto out_of_memory; } #endif @@ -564,7 +567,7 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar #if USE_SNPRINTF /* Prepare checking whether snprintf returns the count via %n. */ - ENSURE_ALLOCATION (length + 1); + ENSURE_ALLOCATION (xsum (length, 1)); result[length] = '\0'; #endif @@ -769,7 +772,8 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar *and* it returns -1 (rather than the length that would have been required) when the buffer is too small. */ - size_t bigger_need = 2 * allocated + 12; + size_t bigger_need = + xsum (xtimes (allocated, 2), 12); ENSURE_ALLOCATION (bigger_need); continue; } @@ -803,10 +807,8 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar /* Need at least count bytes. But allocate proportionally, to avoid looping eternally if snprintf() reports a too small count. */ - size_t n = length + count; - - if (n < 2 * allocated) - n = 2 * allocated; + size_t n = + xmax (xsum (length, count), xtimes (allocated, 2)); ENSURE_ALLOCATION (n); #if USE_SNPRINTF @@ -831,7 +833,7 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar } /* Add the final NUL. */ - ENSURE_ALLOCATION (length + 1); + ENSURE_ALLOCATION (xsum (length, 1)); result[length] = '\0'; if (result != resultbuf && length + 1 < allocated) @@ -848,6 +850,14 @@ VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list ar CLEANUP (); *lengthp = length; return result; + + out_of_memory: + if (!(result == resultbuf || result == NULL)) + free (result); + freea (buf); + CLEANUP (); + errno = ENOMEM; + return NULL; } } diff --git a/m4/ChangeLog b/m4/ChangeLog index 2f2b32fab..fe3163eee 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,7 @@ +2003-11-17 Bruno Haible + + * vasnprintf.m4 (gl_PREREQ_PRINTF_PARSE): Require gt_TYPE_SSIZE_T. + 2003-11-12 Paul Eggert * xalloc.m4 (gl_PREREQ_XALLOC): Do not require gl_SIZE_MAX or @@ -8,7 +12,7 @@ * size_max.m4: New file. * ptrdiff_max.m4: New file. * xsize,m4 (gl_XSIZE): Require gl_SIZE_MAX. - * xalloc.m4 (gl_PREREQ_XALLOC): New file. + * xalloc.m4 (gl_PREREQ_XALLOC): New macro. (gl_XALLOC): Invoke it. 2003-11-04 Bruno Haible diff --git a/m4/vasnprintf.m4 b/m4/vasnprintf.m4 index 2cbee3af9..a0bdc3865 100644 --- a/m4/vasnprintf.m4 +++ b/m4/vasnprintf.m4 @@ -1,4 +1,4 @@ -# vasnprintf.m4 serial 2 +# vasnprintf.m4 serial 3 dnl Copyright (C) 2002-2003 Free Software Foundation, Inc. dnl This file is free software, distributed under the terms of the GNU dnl General Public License. As a special exception to the GNU General @@ -38,6 +38,7 @@ AC_DEFUN([gl_PREREQ_PRINTF_PARSE], AC_REQUIRE([gt_TYPE_WCHAR_T]) AC_REQUIRE([gt_TYPE_WINT_T]) AC_REQUIRE([AC_TYPE_SIZE_T]) + AC_REQUIRE([gt_TYPE_SSIZE_T]) AC_CHECK_TYPES(ptrdiff_t) AC_REQUIRE([gt_AC_TYPE_INTMAX_T]) ]) diff --git a/modules/vasnprintf b/modules/vasnprintf index 5bb22f797..3d72d47e1 100644 --- a/modules/vasnprintf +++ b/modules/vasnprintf @@ -15,10 +15,12 @@ m4/wchar_t.m4 m4/wint_t.m4 m4/longlong.m4 m4/intmax_t.m4 +m4/ssize_t.m4 m4/vasnprintf.m4 Depends-on: alloca +xsize configure.ac: gl_FUNC_VASNPRINTF