Module Name:    src
Committed By:   christos
Date:           Fri Feb 17 19:57:53 UTC 2012

Modified Files:
        src/lib/libc/stdio: vfwprintf.c

Log Message:
Fix: CVE-2012-0864 fprintf() positional argument abuse.
Described in: http://www.phrack.org/issues.html?issue=67&id=9#article
Reported by Stefan Cornelius / Red Hat Security Response Team

- convert internal positional arguments bookkeeping from int to size_t
- provide overflow protection in positional argument spec
- convert loops to memset
- fix memory leaks
- limit positional argument stack offset to the number of arguments required
  by the printf to avoid coredump from va_arg() exhaustion.


To generate a diff of this commit:
cvs rdiff -u -r1.24 -r1.25 src/lib/libc/stdio/vfwprintf.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libc/stdio/vfwprintf.c
diff -u src/lib/libc/stdio/vfwprintf.c:1.24 src/lib/libc/stdio/vfwprintf.c:1.25
--- src/lib/libc/stdio/vfwprintf.c:1.24	Wed Aug 17 05:53:54 2011
+++ src/lib/libc/stdio/vfwprintf.c	Fri Feb 17 14:57:53 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: vfwprintf.c,v 1.24 2011/08/17 09:53:54 christos Exp $	*/
+/*	$NetBSD: vfwprintf.c,v 1.25 2012/02/17 19:57:53 christos Exp $	*/
 
 /*-
  * Copyright (c) 1990, 1993
@@ -38,7 +38,7 @@
 static char sccsid[] = "@(#)vfprintf.c	8.1 (Berkeley) 6/4/93";
 __FBSDID("$FreeBSD: src/lib/libc/stdio/vfwprintf.c,v 1.27 2007/01/09 00:28:08 imp Exp $");
 #else
-__RCSID("$NetBSD: vfwprintf.c,v 1.24 2011/08/17 09:53:54 christos Exp $");
+__RCSID("$NetBSD: vfwprintf.c,v 1.25 2012/02/17 19:57:53 christos Exp $");
 #endif
 #endif /* LIBC_SCCS and not lint */
 
@@ -124,7 +124,7 @@ union arg {
  * Type ids for argument type table.
  */
 enum typeid {
-	T_UNUSED, TP_SHORT, T_INT, T_U_INT, TP_INT,
+	T_UNUSED = 0, TP_SHORT, T_INT, T_U_INT, TP_INT,
 	T_LONG, T_U_LONG, TP_LONG, T_LLONG, T_U_LLONG, TP_LLONG,
 	T_PTRDIFFT, TP_PTRDIFFT, T_SSIZET, T_SIZET, TP_SIZET,
 	T_INTMAXT, T_UINTMAXT, TP_INTMAXT, TP_VOID, TP_CHAR, TP_SCHAR,
@@ -144,7 +144,7 @@ static char	*__wcsconv(wchar_t *, int);
 static int	__sprint(FILE *, struct __suio *);
 #endif
 static int	__find_arguments(const CHAR_T *, va_list, union arg **);
-static int	__grow_type_table(int, enum typeid **, int *);
+static int	__grow_type_table(size_t, enum typeid **, size_t *);
 
 /*
  * Helper function for `fprintf to unbuffered unix file': creates a
@@ -1554,20 +1554,27 @@ __find_arguments(const CHAR_T *fmt0, va_
 {
 	CHAR_T *fmt;		/* format string */
 	int ch;			/* character from fmt */
-	int n, n2;		/* handy integer (short term usage) */
+	size_t n, n2;		/* handy index (short term usage) */
 	CHAR_T *cp;		/* handy char pointer (short term usage) */
 	int flags;		/* flags as above */
 	enum typeid *typetable; /* table of types */
 	enum typeid stattypetable [STATIC_ARG_TBL_SIZE];
-	int tablesize;		/* current size of type table */
-	int tablemax;		/* largest used index in table */
-	int nextarg;		/* 1-based argument index */
+	size_t tablesize;	/* current size of type table */
+	size_t tablemax;	/* largest used index in table */
+	size_t nextarg;		/* 1-based argument index */
+	size_t nitems;		/* number of items we picked from the stack */
 
 	/*
 	 * Add an argument type to the table, expanding if necessary.
+	 * Check for overflow.
 	 */
 #define ADDTYPE(type) \
 	do { \
+		if (nextarg > SIZE_MAX / sizeof(**argtable)) { \
+			if (typetable != stattypetable) \
+				free(typetable); \
+			return -1; \
+		} \
 		if (nextarg >= tablesize) \
 			if (__grow_type_table(nextarg, &typetable, \
 			    &tablesize) == -1) \
@@ -1575,6 +1582,7 @@ __find_arguments(const CHAR_T *fmt0, va_
 		if (nextarg > tablemax) \
 			tablemax = nextarg; \
 		typetable[nextarg++] = type; \
+		nitems++; \
 	} while (/*CONSTCOND*/0)
 
 #define	ADDSARG() \
@@ -1619,7 +1627,7 @@ __find_arguments(const CHAR_T *fmt0, va_
 		cp++; \
 	} \
 	if (*cp == '$') { \
-		int hold = nextarg; \
+		size_t hold = nextarg; \
 		nextarg = n2; \
 		ADDTYPE(T_INT); \
 		nextarg = hold; \
@@ -1628,12 +1636,12 @@ __find_arguments(const CHAR_T *fmt0, va_
 		ADDTYPE(T_INT); \
 	}
 	fmt = (CHAR_T *)__UNCONST(fmt0);
+	memset(stattypetable, 0, sizeof(stattypetable));
 	typetable = stattypetable;
 	tablesize = STATIC_ARG_TBL_SIZE;
 	tablemax = 0; 
 	nextarg = 1;
-	for (n = 0; n < STATIC_ARG_TBL_SIZE; n++)
-		typetable[n] = T_UNUSED;
+	nitems = 1;
 
 	/*
 	 * Scan the format for conversions (`%' character).
@@ -1795,13 +1803,30 @@ reswitch:	switch (ch) {
 	}
 done:
 	/*
+	 * nitems contains the number of arguments we picked from the stack.
+	 * If tablemax is larger, this means that some positional argument,
+	 * tried to pick an argument the number of arguments possibly supplied.
+	 * Since positional arguments are typically used to swap the order of
+	 * the printf arguments and not to pick random arguments from strange
+	 * positions in the stack, we assume that if the positional argument
+	 * is trying to pick beyond the end of arguments, then this is wrong.
+	 * Alternatively we could find a way to figure out when va_arg() runs
+	 * out, but how to do that?
+	 */
+	if (nitems < tablemax) {
+		if (typetable != stattypetable)
+			free(typetable);
+		return -1;
+	}
+	/*
 	 * Build the argument table.
 	 */
 	if (tablemax >= STATIC_ARG_TBL_SIZE) {
-		*argtable = (union arg *)
-		    malloc (sizeof (union arg) * (tablemax + 1));
-		if (*argtable == NULL)
+		*argtable = malloc(sizeof(**argtable) * (tablemax + 1));
+		if (*argtable == NULL) {
+			free(typetable);
 			return -1;
+		}
 	}
 
 	(*argtable) [0].intarg = 0;
@@ -1892,7 +1917,7 @@ done:
 		}
 	}
 
-	if ((typetable != NULL) && (typetable != stattypetable))
+	if (typetable != stattypetable)
 		free (typetable);
 	return 0;
 }
@@ -1901,28 +1926,27 @@ done:
  * Increase the size of the type table.
  */
 static int
-__grow_type_table (int nextarg, enum typeid **typetable, int *tablesize)
+__grow_type_table (size_t nextarg, enum typeid **typetable, size_t *tablesize)
 {
 	enum typeid *const oldtable = *typetable;
 	const int oldsize = *tablesize;
 	enum typeid *newtable;
-	int n, newsize = oldsize * 2;
+	size_t n, newsize = oldsize * 2;
 
 	if (newsize < nextarg + 1)
 		newsize = nextarg + 1;
 	if (oldsize == STATIC_ARG_TBL_SIZE) {
-		if ((newtable = malloc(newsize * sizeof(enum typeid))) == NULL)
+		if ((newtable = malloc(newsize * sizeof(*newtable))) == NULL)
 			return -1;
-		memcpy(newtable, oldtable, oldsize * sizeof(enum typeid));
+		memcpy(newtable, oldtable, oldsize * sizeof(*newtable));
 	} else {
-		newtable = realloc(oldtable, newsize * sizeof(enum typeid));
+		newtable = realloc(oldtable, newsize * sizeof(*newtable));
 		if (newtable == NULL) {
 			free(oldtable);
 			return -1;
 		}
 	}
-	for (n = oldsize; n < newsize; n++)
-		newtable[n] = T_UNUSED;
+	memset(&newtable[oldsize], 0, (newsize - oldsize) * sizeof(*newtable));
 
 	*typetable = newtable;
 	*tablesize = newsize;

Reply via email to