libratbox: Fix undefined behaviour advancing pointer beyond end of array.

The C standard does not allow constructing pointers beyond one past the end
of an array. Therefore, if size is an unsigned type (size_t), then
buf + size is never less than buf.

Clang on 32-bit took advantage of the undefined behaviour, causing
segfaults.

Lightly tested.
This commit is contained in:
Jilles Tjoelker 2014-02-16 16:06:01 +01:00
parent 7f2508c135
commit fab79c5d25

View file

@ -151,8 +151,8 @@ put_dec(char *buf, unsigned long long int num)
#define SPECIAL 32 /* 0x */ #define SPECIAL 32 /* 0x */
#define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */ #define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */
static char * static size_t
number(char *buf, char *end, unsigned long long int num, int base, int size, int precision, number(char *const buf, const size_t size, size_t idx, unsigned long long int num, int base, int field_width, int precision,
int type) int type)
{ {
char sign, tmp[66]; char sign, tmp[66];
@ -167,7 +167,7 @@ number(char *buf, char *end, unsigned long long int num, int base, int size, int
if(type & LEFT) if(type & LEFT)
type &= ~ZEROPAD; type &= ~ZEROPAD;
if(base < 2 || base > 36) if(base < 2 || base > 36)
return NULL; return idx;
sign = 0; sign = 0;
if(type & SIGN) if(type & SIGN)
{ {
@ -175,24 +175,24 @@ number(char *buf, char *end, unsigned long long int num, int base, int size, int
{ {
sign = '-'; sign = '-';
num = -(signed long long int)num; num = -(signed long long int)num;
size--; field_width--;
} }
else if(type & PLUS) else if(type & PLUS)
{ {
sign = '+'; sign = '+';
size--; field_width--;
} }
else if(type & SPACE) else if(type & SPACE)
{ {
sign = ' '; sign = ' ';
size--; field_width--;
} }
} }
if(need_pfx) if(need_pfx)
{ {
size--; field_width--;
if(base == 16) if(base == 16)
size--; field_width--;
} }
/* generate full string in tmp[], in reverse order */ /* generate full string in tmp[], in reverse order */
@ -226,69 +226,69 @@ number(char *buf, char *end, unsigned long long int num, int base, int size, int
if(i > precision) if(i > precision)
precision = i; precision = i;
/* leading space padding */ /* leading space padding */
size -= precision; field_width -= precision;
if(!(type & (ZEROPAD + LEFT))) if(!(type & (ZEROPAD + LEFT)))
{ {
while(--size >= 0) while(--field_width >= 0)
{ {
if(buf < end) if(idx < size)
*buf = ' '; buf[idx] = ' ';
++buf; ++idx;
} }
} }
/* sign */ /* sign */
if(sign) if(sign)
{ {
if(buf < end) if(idx < size)
*buf = sign; buf[idx] = sign;
++buf; ++idx;
} }
/* "0x" / "0" prefix */ /* "0x" / "0" prefix */
if(need_pfx) if(need_pfx)
{ {
if(buf < end) if(idx < size)
*buf = '0'; buf[idx] = '0';
++buf; ++idx;
if(base == 16) if(base == 16)
{ {
if(buf < end) if(idx < size)
*buf = digits[16]; /* for arbitrary base: digits[33]; */ buf[idx] = digits[16]; /* for arbitrary base: digits[33]; */
++buf; ++idx;
} }
} }
/* zero or space padding */ /* zero or space padding */
if(!(type & LEFT)) if(!(type & LEFT))
{ {
char c = (type & ZEROPAD) ? '0' : ' '; char c = (type & ZEROPAD) ? '0' : ' ';
while(--size >= 0) while(--field_width >= 0)
{ {
if(buf < end) if(idx < size)
*buf = c; buf[idx] = c;
++buf; ++idx;
} }
} }
/* hmm even more zero padding? */ /* hmm even more zero padding? */
while(i <= --precision) while(i <= --precision)
{ {
if(buf < end) if(idx < size)
*buf = '0'; buf[idx] = '0';
++buf; ++idx;
} }
/* actual digits of result */ /* actual digits of result */
while(--i >= 0) while(--i >= 0)
{ {
if(buf < end) if(idx < size)
*buf = tmp[i]; buf[idx] = tmp[i];
++buf; ++idx;
} }
/* trailing space padding */ /* trailing space padding */
while(--size >= 0) while(--field_width >= 0)
{ {
if(buf < end) if(idx < size)
*buf = ' '; buf[idx] = ' ';
++buf; ++idx;
} }
return buf; return idx;
} }
/** /**
@ -315,7 +315,8 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
int len; int len;
unsigned long long int num; unsigned long long int num;
int i, base; int i, base;
char *str, *end, c; char c;
size_t idx;
const char *s; const char *s;
int flags; /* flags to number() */ int flags; /* flags to number() */
@ -330,28 +331,20 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
/* Reject out-of-range values early. Large positive sizes are /* Reject out-of-range values early. Large positive sizes are
used for unknown buffer sizes. */ used for unknown buffer sizes. */
if(rb_unlikely((int)size < 0)) if(rb_unlikely(size > INT_MAX))
{ {
return 0; return 0;
} }
str = buf; idx = 0;
end = buf + size;
/* Make sure end is always >= buf */
if(end < buf)
{
end = ((void *)-1);
size = end - buf;
}
for(; *fmt; ++fmt) for(; *fmt; ++fmt)
{ {
if(*fmt != '%') if(*fmt != '%')
{ {
if(str < end) if(idx < size)
*str = *fmt; buf[idx] = *fmt;
++str; ++idx;
continue; continue;
} }
@ -435,20 +428,20 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{ {
while(--field_width > 0) while(--field_width > 0)
{ {
if(str < end) if(idx < size)
*str = ' '; buf[idx] = ' ';
++str; ++idx;
} }
} }
c = (unsigned char)va_arg(args, int); c = (unsigned char)va_arg(args, int);
if(str < end) if(idx < size)
*str = c; buf[idx] = c;
++str; ++idx;
while(--field_width > 0) while(--field_width > 0)
{ {
if(str < end) if(idx < size)
*str = ' '; buf[idx] = ' ';
++str; ++idx;
} }
continue; continue;
@ -464,23 +457,23 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{ {
while(len < field_width--) while(len < field_width--)
{ {
if(str < end) if(idx < size)
*str = ' '; buf[idx] = ' ';
++str; ++idx;
} }
} }
for(i = 0; i < len; ++i) for(i = 0; i < len; ++i)
{ {
if(str < end) if(idx < size)
*str = *s; buf[idx] = *s;
++str; ++idx;
++s; ++s;
} }
while(len < field_width--) while(len < field_width--)
{ {
if(str < end) if(idx < size)
*str = ' '; buf[idx] = ' ';
++str; ++idx;
} }
continue; continue;
@ -490,7 +483,7 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
field_width = 2 * sizeof(void *); field_width = 2 * sizeof(void *);
flags |= ZEROPAD; flags |= ZEROPAD;
} }
str = number(str, end, idx = number(buf, size, idx,
(unsigned long)va_arg(args, void *), (unsigned long)va_arg(args, void *),
16, field_width, precision, flags); 16, field_width, precision, flags);
continue; continue;
@ -502,24 +495,24 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
if(qualifier == 'l') if(qualifier == 'l')
{ {
long *ip = va_arg(args, long *); long *ip = va_arg(args, long *);
*ip = (str - buf); *ip = idx;
} }
else if(qualifier == 'Z' || qualifier == 'z') else if(qualifier == 'Z' || qualifier == 'z')
{ {
size_t *ip = va_arg(args, size_t *); size_t *ip = va_arg(args, size_t *);
*ip = (str - buf); *ip = idx;
} }
else else
{ {
int *ip = va_arg(args, int *); int *ip = va_arg(args, int *);
*ip = (str - buf); *ip = idx;
} }
continue; continue;
case '%': case '%':
if(str < end) if(idx < size)
*str = '%'; buf[idx] = '%';
++str; ++idx;
continue; continue;
/* integer number formats - set up the flags and "break" */ /* integer number formats - set up the flags and "break" */
@ -540,14 +533,14 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
break; break;
default: default:
if(str < end) if(idx < size)
*str = '%'; buf[idx] = '%';
++str; ++idx;
if(*fmt) if(*fmt)
{ {
if(str < end) if(idx < size)
*str = *fmt; buf[idx] = *fmt;
++str; ++idx;
} }
else else
{ {
@ -583,17 +576,17 @@ rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
if(flags & SIGN) if(flags & SIGN)
num = (signed int)num; num = (signed int)num;
} }
str = number(str, end, num, base, field_width, precision, flags); idx = number(buf, size, idx, num, base, field_width, precision, flags);
} }
if(size > 0) if(size > 0)
{ {
if(str < end) if(idx < size)
*str = '\0'; buf[idx] = '\0';
else else
end[-1] = '\0'; buf[size - 1] = '\0';
} }
/* the trailing null byte doesn't count towards the total */ /* the trailing null byte doesn't count towards the total */
return str - buf; return idx;
} }
/** /**