SecurityTracker.com
Keep Track of the Latest Vulnerabilities
with SecurityTracker!
    Home    |    View Topics    |    Search    |    Contact Us    |   

SecurityTracker
Archives


 
Sign Up
Sign Up for Your FREE Weekly SecurityTracker E-mail Alert Summary
Instant Alerts
Buy our Premium Vulnerability Notification Service to receive customized, instant alerts
Affiliates
Put SecurityTracker Vulnerability Alerts on Your Web Site -- It's Free!
Partners
Become a Partner and License Our Database or Notification Service
Report a Bug
Report a vulnerability that you have found to SecurityTracker
bugs
@
securitytracker.com






Category:   Application (Generic)  >   libc Vendors:   GNU [multiple authors]
libc strfmon() Integer Overflows May Let Users Execute Arbitrary Code
SecurityTracker Alert ID:  1019722
SecurityTracker URL:  http://securitytracker.com/id/1019722
CVE Reference:   CVE-2008-1391   (Links to External Site)
Date:  Mar 27 2008
Impact:   Execution of arbitrary code via network, User access via network


Description:   A vulnerability was reported in libc. A user can cause arbitrary code to be executed on the target system.

A remote user can send a specially crafted value that, when processed by the target application that uses libc, will trigger an integer overflow and execute arbitrary code on the target system. The code will run with the privileges of the target application.

Applications that use the strfmon() function are affected.

Maksymilian Arciemowicz (cxib) of SecurityReason.com reported this vulnerability.

The original advisory is available at:

http://securityreason.com/achievement_securityalert/53

Impact:   A user can cause arbitrary code to be executed on the target system. The specific impact depends on the application using libc.
Solution:   No upstream solution was available at the time of this entry.
Cause:   Boundary error
Underlying OS:   UNIX (BSD/OS), UNIX (FreeBSD), UNIX (NetBSD), UNIX (OpenBSD), UNIX (OS X)

Message History:   This archive entry has one or more follow-up message(s) listed below.
Apr 22 2008 (NetBSD Issues Fix) libc strfmon() Integer Overflows May Let Users Execute Arbitrary Code   (NetBSD Security-Officer <security-officer@NetBSD.org>)
NetBSD has released a fix for NetBSD 4.0.



 Source Message Contents

Date:  27 Mar 2008 14:09:49 -0000
Subject:  [securityreason] *BSD libc (strfmon) Multiple vulnerabilities

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

[ *BSD libc (strfmon) Multiple vulnerabilities ]

Author: Maksymilian Arciemowicz (cxib)
SecurityReason.com
Date:
- - Written: 10.03.2008
- - Public:  25.03.2008

SecurityReason Research
SecurityAlert Id: 53

CVE: CVE-2008-1391
SecurityRisk: High

Affected Software: 
FreeBSD lines: 6,7
NetBSD 4
another systems what use this functions.
Standard C Library (libc, -lc) for BSD
probably some MacOS version

Advisory URL:
http://securityreason.com/achievement_securityalert/53
Vendor: http://www.php.net

- --- 0.Description ---
strfmon -- convert monetary value to string

The strfmon() function places characters into the array pointed to by s as controlled by the string pointed to by format.  No more
 than maxsize bytes are placed into the array.

The format string is composed of zero or more directives: ordinary characters (not %), which are copied unchanged to the output stream;
 and conversion specifications, each of which results in fetching zero or more subsequent arguments.  Each conversion specification
 is introduced by the % character.

SYNOPSIS:

#include <monetary.h>

     ssize_t
     strfmon(char * restrict s, size_t maxsize, const char * restrict format,
         ...);

- --- 1. /usr/src/lib/libc/stdlib/strfmon.c - Integer Overflow ---
The main problem and vulnerability exist in strfmon() function. When we use this function in example program:

- ---example-start--
#include <stdio.h>
#include <monetary.h>

int main(int argc, char* argv[]){
	char buff[51];
	char *bux=buff;
	int res;
	
	res=strfmon(bux, 50, argv[1], "0");
	return 0;
}
- ---example-end--

and compile it, we can manipulate format string.

Let's try to run example:
cxib# ./pln %99999999999999999999n
Segmentation fault (core dumped)

What is wrong? Let's see

cxib# gdb -q pln
(no debugging symbols found)...(gdb) r %99999999999999999999n
Starting program: /cxib/C/pln %99999999999999999999n
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x2814e0e6 in memmove () from /lib/libc.so.7
(gdb)

memmove() will bad reallocation memory. 

cxib# gdb -q pln
(no debugging symbols found)...(gdb) r %.9999999999n
Starting program: /cxib/C/pln %.9999999999n
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x2814f093 in abort () from /lib/libc.so.7


Next example is :

cxib# ./pln %#99999999999999999999n

Long execution time. Let's try check this process :
- --------------------------
cxib# ps -aux | grep pln
cxib   1843 89.1 13.2 140320 119588  p2  R+    4:29PM   0:09.68 ./pln %#99999999999999999999n
cxib# ps -aux | grep pln
cxib   1843 94.7 48.4 482336 438236  p2  R+    4:29PM   1:54.07 ./pln %#99999999999999999999n

1 VSZ=140320
2 VSZ=482336

- ----------------------------

Why? pln will allocate more memory that we have. PHP use strfmon() in money_format() function. When we use mod_php5 in apache, we
 can create example exploit.. result will be :

- ---apache-child-die---
swap_pager: out of swap space
swap_pager_getswapspace(16): failed
Mar 15 21:03:23 cxib kernel: pid 1210 (httpd), uid 80, was killed: out of swap space
- ---apache-child-die---

Difference between %99999999999999999999n and (%#99999999999999999999n or %.9999999999n) is "#" or "."

     o   A `#' sign followed by a decimal number specifying the maximum
         expected number of digits after the radix character.
     o   A `.' character followed by a decimal number specifying the number
         the number of digits after the radix character.

Let's see the source of strfmon() function :

- ---strfmon()-start---
ssize_t
strfmon(char * __restrict s, size_t maxsize, const char * __restrict format,
    ...)
{
	va_list		ap;
	char 		*dst;		/* output destination pointer */
	const char 	*fmt;		/* current format poistion pointer */
	struct lconv 	*lc;		/* pointer to lconv structure */
	char		*asciivalue;	/* formatted double pointer */

	int		flags;		/* formatting options */
	int		pad_char;	/* padding character */
	int		pad_size;	/* pad size */
	int		width;		/* field width */
	int		left_prec;	/* left precision */
	int		right_prec;	/* right precision */
	double		value;		/* just value */
	char		space_char = ' '; /* space after currency */

	char		cs_precedes,	/* values gathered from struct lconv */
			sep_by_space,
			sign_posn,
			*signstr,
			*currency_symbol;

	char		*tmpptr;	/* temporary vars */
	int		sverrno;

        va_start(ap, format);

	lc = localeconv();
	dst = s;
	fmt = format;
	asciivalue = NULL;
	currency_symbol = NULL;
	pad_size = 0;

	while (*fmt) {
		/* pass nonformating characters AS IS */
		if (*fmt != '%')
			goto literal;

		/* '%' found ! */

		/* "%%" mean just '%' */
		if (*(fmt+1) == '%') {
			fmt++;
	literal:
			PRINT(*fmt++);
			continue;
		}

		/* set up initial values */
		flags = (NEED_GROUPING|LOCALE_POSN);
		pad_char = ' ';		/* padding character is "space" */
		left_prec = -1;		/* no left precision specified */
		right_prec = -1;	/* no right precision specified */
		width = -1;		/* no width specified */
		value = 0;		/* we have no value to print now */

		/* Flags */
		while (1) {
			switch (*++fmt) {
				case '=':	/* fill character */
					pad_char = *++fmt;
					if (pad_char == '\0')
						goto format_error;
					continue;
				case '^':	/* not group currency  */
					flags &= ~(NEED_GROUPING);
					continue;
				case '+':	/* use locale defined signs */
					if (flags & SIGN_POSN_USED)
						goto format_error;
					flags |= (SIGN_POSN_USED|LOCALE_POSN);
					continue;
				case '(':	/* enclose negatives with () */
					if (flags & SIGN_POSN_USED)
						goto format_error;
					flags |= (SIGN_POSN_USED|PARENTH_POSN);
					continue;
				case '!':	/* suppress currency symbol */
					flags |= SUPRESS_CURR_SYMBOL;
					continue;
				case '-':	/* alignment (left)  */
					flags |= LEFT_JUSTIFY;
					continue;
				default:
					break;
			}
			break;
		}

		/* field Width */
		if (isdigit((unsigned char)*fmt)) {
			GET_NUMBER(width);
			/* Do we have enough space to put number with
			 * required width ?
			 */
			if (dst + width >= s + maxsize)
				goto e2big_error;
		}

		/* Left precision */
		if (*fmt == '#') {
			if (!isdigit((unsigned char)*++fmt))
				goto format_error;
			GET_NUMBER(left_prec);
		}

		/* Right precision */
		if (*fmt == '.') {
			if (!isdigit((unsigned char)*++fmt))
				goto format_error;
			GET_NUMBER(right_prec);
		}

		/* Conversion Characters */
		switch (*fmt++) {
			case 'i':	/* use internaltion currency format */
				flags |= USE_INTL_CURRENCY;
				break;
			case 'n':	/* use national currency format */
				flags &= ~(USE_INTL_CURRENCY);
				break;
			default:	/* required character is missing or
					   premature EOS */
				goto format_error;
		}

		if (flags & USE_INTL_CURRENCY) {
			currency_symbol = strdup(lc->int_curr_symbol);
			if (currency_symbol != NULL)
				space_char = *(currency_symbol+3);
		} else
			currency_symbol = strdup(lc->currency_symbol);

		if (currency_symbol == NULL)
			goto end_error;			/* ENOMEM. */

		/* value itself */
		value = va_arg(ap, double);

		/* detect sign */
		if (value < 0) {
			flags |= IS_NEGATIVE;
			value = -value;
		}

		/* fill left_prec with amount of padding chars */
		if (left_prec >= 0) {
			pad_size = __calc_left_pad((flags ^ IS_NEGATIVE),
							currency_symbol) -
				   __calc_left_pad(flags, currency_symbol);
			if (pad_size < 0)
				pad_size = 0;
		}

		asciivalue = __format_grouped_double(value, &flags,
				left_prec, right_prec, pad_char);
		if (asciivalue == NULL)
			goto end_error;		/* errno already set     */
						/* to ENOMEM by malloc() */

		/* set some variables for later use */
		__setup_vars(flags, &cs_precedes, &sep_by_space,
				&sign_posn, &signstr);

		/*
		 * Description of some LC_MONETARY's values:
		 *
		 * p_cs_precedes & n_cs_precedes
		 *
		 * = 1 - $currency_symbol precedes the value
		 *       for a monetary quantity with a non-negative value
		 * = 0 - symbol succeeds the value
		 *
		 * p_sep_by_space & n_sep_by_space
                 *
		 * = 0 - no space separates $currency_symbol
		 *       from the value for a monetary quantity with a
		 *	 non-negative value
		 * = 1 - space separates the symbol from the value
		 * = 2 - space separates the symbol and the sign string,
		 *       if adjacent.
                 *
		 * p_sign_posn & n_sign_posn
                 *
		 * = 0 - parentheses enclose the quantity and the
		 *	 $currency_symbol
		 * = 1 - the sign string precedes the quantity and the 
		 *       $currency_symbol
		 * = 2 - the sign string succeeds the quantity and the 
		 *       $currency_symbol
		 * = 3 - the sign string precedes the $currency_symbol
		 * = 4 - the sign string succeeds the $currency_symbol
                 *
		 */

		tmpptr = dst;

		while (pad_size-- > 0)
			PRINT(' ');

		if (sign_posn == 0 && (flags & IS_NEGATIVE))
			PRINT('(');

		if (cs_precedes == 1) {
			if (sign_posn == 1 || sign_posn == 3) {
				PRINTS(signstr);
				if (sep_by_space == 2)		/* XXX: ? */
					PRINT(' ');
			}

			if (!(flags & SUPRESS_CURR_SYMBOL)) {
				PRINTS(currency_symbol);

				if (sign_posn == 4) {
					if (sep_by_space == 2)
						PRINT(space_char);
					PRINTS(signstr);
					if (sep_by_space == 1)
						PRINT(' ');
				} else if (sep_by_space == 1)
					PRINT(space_char);
			}
		} else if (sign_posn == 1)
			PRINTS(signstr);

		PRINTS(asciivalue);

		if (cs_precedes == 0) {
			if (sign_posn == 3) {
				if (sep_by_space == 1)
					PRINT(' ');
				PRINTS(signstr);
			}

			if (!(flags & SUPRESS_CURR_SYMBOL)) {
				if ((sign_posn == 3 && sep_by_space == 2)
				    || (sep_by_space == 1
				    && (sign_posn == 0
				    || sign_posn == 1
				    || sign_posn == 2
				    || sign_posn == 4)))
					PRINT(space_char);
				PRINTS(currency_symbol); /* XXX: len */
				if (sign_posn == 4) {
					if (sep_by_space == 2)
						PRINT(' ');
					PRINTS(signstr);
				}
			}
		}

		if (sign_posn == 2) {
			if (sep_by_space == 2)
				PRINT(' ');
			PRINTS(signstr);
		}

		if (sign_posn == 0 && (flags & IS_NEGATIVE))
			PRINT(')');

		if (dst - tmpptr < width) {
			if (flags & LEFT_JUSTIFY) {
				while (dst - tmpptr < width)
					PRINT(' ');
			} else {
				pad_size = dst-tmpptr;
				memmove(tmpptr + width-pad_size, tmpptr,
				    pad_size);
				memset(tmpptr, ' ', width-pad_size);
				dst += width-pad_size;
			}
		}
	}

	PRINT('\0');
	va_end(ap);
	free(asciivalue);
	free(currency_symbol);
	return (dst - s - 1);	/* return size of put data except trailing '\0' */

e2big_error:
	errno = E2BIG;
	goto end_error;

format_error:
	errno = EINVAL;

end_error:
	sverrno = errno;
	if (asciivalue != NULL)
		free(asciivalue);
	if (currency_symbol != NULL)
		free(currency_symbol);
	errno = sverrno;
	va_end(ap);
	return (-1);
}
- ---strfmon()-end---

As we can see locks are corrected, but function GET_NUMBER()

- ---GET_NUMBER()-start---
#define GET_NUMBER(VAR)	do {					\
	VAR = 0;						\
	while (isdigit((unsigned char)*fmt)) {			\
		VAR *= 10;					\
		VAR += *fmt - '0';				\
		fmt++;						\
	}							\
} while (0)
- ---GET_NUMBER()-end---

fmt=2147483647n => GET_NUMBER(2147483647)
fmt=2147483648n => GET_NUMBER(-2147483648)
fmt=2147483649n => GET_NUMBER(-2147483647)
fmt=4294967296n => GET_NUMBER(0)
fmt=4294967297n => GET_NUMBER(1)

We have integer overflow.

Next problem is with int left_prec and right_prec. Sum of this int's isn't checked.


Problem exist also in printf() function.

Example code will show Integer Overflow .

- ---example-start--
#include <stdio.h>

int
main(int argc, char *argv[])
{
printf("%1410065408.1410065407f\n", 2);
return 0;
}
- ---example-end--

cxib# gcc -o pln pln.c && ./pln
Segmentation fault (core dumped)

What is wrong? the same problem that was in strfmon() function.

- ---
/* convert to string */
	snprintf(fmt, sizeof(fmt), "%%%d.%df", left_prec + right_prec + 1,
	    right_prec);
	avalue_size = asprintf(&avalue, fmt, value);
- ---

fmt is here 32 chars table. So for format like

strfmon(bux, 50, "%.10n", "1.1");

fmt will have %11.10f

for
	res=strfmon(bux, 50, "%.1410065407n", "1.1");

will be crash here 
	avalue_size = asprintf(&avalue, fmt, value);

fmt=%1410065408.1410065407f
value=1.1

it is possible? asprintf(&avalue, "%.1410065407f", "1.1");
and the question is why? Let's see to gdb

cxib# gdb -q pln
(no debugging symbols found)...(gdb) r
Starting program: /cxib/C/pln 
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x2814f0a3 in abort () from /lib/libc.so.7

SecurityReason will only alarm all BSDs developers to check libc. A lot of programs (with strfmon function) should be recompiled after
 libc updated. We have informed NetBSD and FreeBSD teams, but we don't know how exactly use this functions. The priority of strfmon()
 issues are very High. 

- --- 2. Exploit ---
SecurityReason will not public official exploit for this issue.

- --- 3. How to fix ---
We have informed NetBSD teams about it. strfmon() function is fixed in

http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/stdlib/strfmon.c

but we are not sure of it is correct. 

- --- 4. Greets ---
sp3x Infospec p_e_a Chujwamwdupe schain and dr Truderung (za ndst z C)

- --- 5. Contact ---
Author: SecurityReason [ Maksymilian Arciemowicz ]
Email: cxib [at] securityreason [dot] com
GPG: http://securityreason.com/key/Arciemowicz.Maksymilian.gpg [NEW KEY]
GPG: http://securityreason.com/key/Arciemowicz.Maksymilian.gpg.old [OLD KEY]
http://securityreason.com
http://securityreason.pl

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.4 (FreeBSD)

iD8DBQFH6Un9W1OhNJH6DMURAsSEAJwMuWlEKrzHinBM1ojAxGIFOHohswCfZhxM
HcKAw4DGKq13jrhrwsP0BF4=
=ifF2
-----END PGP SIGNATURE-----

 
 


Go to the Top of This SecurityTracker Archive Page





Home   |    View Topics   |    Search   |    Contact Us

Copyright 2014, SecurityGlobal.net LLC