Bug 4977 - SEGV in strlen() of string argument of vsnprintf call on RHEL WS3/64-bit
Summary: SEGV in strlen() of string argument of vsnprintf call on RHEL WS3/64-bit
Status: RESOLVED INVALID
Alias: None
Product: glibc
Classification: Unclassified
Component: libc (show other bugs)
Version: unspecified
: P2 critical
Target Milestone: ---
Assignee: Ulrich Drepper
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-08-29 15:28 UTC by Tim
Modified: 2014-07-04 15:59 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Tim 2007-08-29 15:28:53 UTC
As per summary, a call to vsnprintf such as

vsnprintf( buf, nchar, fmt, ap )

where the va_list 'ap' has a C string argument is causing a SEGV in the strlen.
Typical stack

Program received signal SIGSEGV, Segmentation fault.
0x0000002a95707290 in strlen () from /lib64/tls/libc.so.6
(gdb) bt
#0  0x0000002a95707290 in strlen () from /lib64/tls/libc.so.6
#1  0x0000002a956d45b0 in vfprintf () from /lib64/tls/libc.so.6
#2  0x0000002a956f8949 in vsnprintf () from /lib64/tls/libc.so.6

Source code for a trivial reproducible case below, compiled on (uname -a)

Linux hpopteron 2.4.21-50.ELsmp #1 SMP Tue May 8 17:10:20 EDT 2007 x86_64 x86_64
x86_64 GNU/Linux

with

gcc --std=c9x -Wall -pedantic -g -o vsn vsn.c

using

gcc (GCC) 3.2.3 20030502 (Red Hat Linux 3.2.3-59)
GNU C Library stable release version 2.3.2, by Roland McGrath et al.

Run as

./vsn "foo"

Code follows 

---------------------%<------------------------
#include <stdio.h>
#include <stdarg.h>
#include <malloc.h>

int vssprintf
(
 char **	userBufPtr,
 const char *	format,
 va_list 	ap
)
{
    if( userBufPtr )
    {
	/* see how many formatted characters there are to process,
	 * this should work even if the format is "" or equivalent
	 * eg. ( "%s", "" ) */
	int nchar = vsnprintf( NULL, 0, format, ap ) + 1;
	char * bufPtr = (char *)malloc( nchar );

	nchar = vsnprintf( bufPtr, nchar, format, ap );

	*userBufPtr = bufPtr ; 
	return( nchar );
    }
    /* IO errors in system ..printf() funtions should normally trigger the
     * return of a negative number (e.g. in ANSI / ISO C) - do the same here */
    return -1 ;
}

int ssprintf
(
 char **	userBufPtr,
 const char *	format,
 ... 
)
{
    int	nchar = -1 ;
    if( userBufPtr )
    {
	va_list	ap;

	/* Unravel the format, args, ... into a buffer */
	va_start( ap, format );

	nchar = vssprintf( userBufPtr, format, ap );

	/* Clean up */
	va_end(ap);
    }

    return( nchar );
}


int main( int argc, char *argv[] )
{
    int n = 0 ;
    if( argc > 1 )
    {
	char * buf ;
	n = ssprintf( &buf, "Hello %s !\n", argv[1] );
	fprintf( stderr, "Printed %d characters from va_list\n", n ) ;
	n = printf( "%s\n", buf ) ;
    }
    return n ;
}
---------------------%<------------------------

Under Valgrind I get the following stack

--2099-- Reading syms from
/usr/local/lib/valgrind/amd64-linux/vgpreload_memcheck.so (0x4A17000)
--2099-- REDIR: 0x40101A0 (index) redirected to 0x4A1A7E0 (index)
--2099-- REDIR: 0x4010350 (strcmp) redirected to 0x4A1AC80 (strcmp)
--2099-- REDIR: 0x4010380 (strlen) redirected to 0x4A1AA10 (strlen)
--2099-- Reading syms from /lib64/tls/libc-2.3.2.so (0x4B1D000)
--2099-- REDIR: 0x4B9B620 (rindex) redirected to 0x4A1A700 (rindex)
--2099-- REDIR: 0x4B9B280 (strlen) redirected to 0x4A1A9D0 (strlen)
--2099-- REDIR: 0x4B93540 (malloc) redirected to 0x4A18C6F (malloc)
==2099== Invalid read of size 1
==2099==    at 0x4A1A9D2: strlen (mac_replace_strmem.c:243)
==2099==    by 0x4B685AF: vfprintf (in /lib64/tls/libc-2.3.2.so)
==2099==    by 0x4B8C948: vsnprintf (in /lib64/tls/libc-2.3.2.so)
==2099==    by 0x400600: vssprintf (vsn.c:20)
==2099==    by 0x40070A: ssprintf (vsn.c:45)
==2099==    by 0x400752: main (vsn.c:61)
==2099==  Address 0x2 is not stack'd, malloc'd or (recently) free'd
==2099== 
==2099== Process terminating with default action of signal 11 (SIGSEGV)
==2099==  Access not within mapped region at address 0x2
==2099==    at 0x4A1A9D2: strlen (mac_replace_strmem.c:243)
==2099==    by 0x4B685AF: vfprintf (in /lib64/tls/libc-2.3.2.so)
==2099==    by 0x4B8C948: vsnprintf (in /lib64/tls/libc-2.3.2.so)
==2099==    by 0x400600: vssprintf (vsn.c:20)
==2099==    by 0x40070A: ssprintf (vsn.c:45)
==2099==    by 0x400752: main (vsn.c:61)
--2099-- REDIR: 0x4B93710 (free) redirected to 0x4A197E1 (free)
--2099-- REDIR: 0x4B9CEA0 (memset) redirected to 0x4A1B140 (memset)
==2099== 
==2099== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 3 from 2)
==2099== 
==2099== 1 errors in context 1 of 1:
==2099== Invalid read of size 1
==2099==    at 0x4A1A9D2: strlen (mac_replace_strmem.c:243)
==2099==    by 0x4B685AF: vfprintf (in /lib64/tls/libc-2.3.2.so)
==2099==    by 0x4B8C948: vsnprintf (in /lib64/tls/libc-2.3.2.so)
==2099==    by 0x400600: vssprintf (vsn.c:20)
==2099==    by 0x40070A: ssprintf (vsn.c:45)
==2099==    by 0x400752: main (vsn.c:61)
==2099==  Address 0x2 is not stack'd, malloc'd or (recently) free'd
--2099-- 
--2099-- supp:    1 strlen-not-intercepted-early-enough-HACK-5
--2099-- supp:    2 dl_relocate_object
==2099== 
==2099== IN SUMMARY: 1 errors from 1 contexts (suppressed: 3 from 2)
==2099== 
==2099== malloc/free: in use at exit: 13 bytes in 1 blocks.
==2099== malloc/free: 1 allocs, 0 frees, 13 bytes allocated.
==2099== 

.............

Looks like strlen() is being given duff info. Quick Google reveals similar
bug/stack seen reported under Nessus, gnash and Firefly forums this year.
Reproduced on

- SuSE 10.1 / 64-bit platform (no details)

- RHEL WS4/64
gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-3)
Linux tornado 2.6.9-42.EL #1 Tue Aug 15 09:30:48 BST 2006 x86_64 x86_64 x86_64
GNU/Linux
GNU C Library stable release version 2.3.4, by Roland McGrath et al.


Cheers
Tim
Comment 1 Jakub Jelinek 2007-08-29 15:40:15 UTC
The testcase is invalid.
Please read http://www.opengroup.org/onlinepubs/009695399/basedefs/stdarg.h.html
carefully.
Particularly you are missing va_copy and another va_end.
Comment 2 Tim 2007-08-29 15:58:22 UTC
(In reply to comment #1)
> The testcase is invalid.
> Please read http://www.opengroup.org/onlinepubs/009695399/basedefs/stdarg.h.html
> carefully.

Agh - stupidity hits. Thanks for the link - I guess this means passing the
va_list as a function parameter is highly implementation specific and will
potentially be unavailable on some systems (e.g. HP-UX 11i even with recent
compilers has no va_copy) or with no c9x compliant compilers.

> Particularly you are missing va_copy and another va_end.

yep - on C99 systems. That went past a few of us, should have realised.

Cheers
tim
Comment 3 Jakub Jelinek 2007-08-29 16:10:06 UTC
Passing it once to some function is just fine.  What your testcase does
wrong is passing the same va_list object to another function, while the standards
say that if you pass it to some function and that function uses va_arg on it,
then the object can be only passed to va_end, nothing else.
On some architectures it can work even multiple times, that's mainly
architectures which define va_list as a pointer (e.g. i386), but note
that doing so is highly unportable.  On other architectures, va_list as is
a one element array containing some structure (e.g. x86_64).  If you pass
that to some function, you just pass its address to it, the object actually
resides in the function which invoked va_start or va_copy, if their argument
is an automatic va_list variable.  So, the first vsnprintf will modify that
va_list object to point after all arguments it consumed and when you call
vsnprintf again with the same va_list (== the same address of some automatic
struct), it will start looking at garbage.
Comment 4 Tim 2007-08-29 16:34:04 UTC
(In reply to comment #3)
> Passing it once to some function is just fine.  What your testcase does
> wrong is passing the same va_list object to another function, while the standards
> say that if you pass it to some function and that function uses va_arg on it,
> then the object can be only passed to va_end, nothing else.
> On some architectures it can work even multiple times, 

yep - that's what I was alluding too - poor language on my part 

> that's mainly
> architectures which define va_list as a pointer (e.g. i386), but note
> that doing so is highly unportable.  

indeed - it's one thing that I hadn't fully appreciated until now

> On other architectures, va_list as is
> a one element array containing some structure (e.g. x86_64).  If you pass
> that to some function, you just pass its address to it, the object actually
> resides in the function which invoked va_start or va_copy, if their argument
> is an automatic va_list variable.  So, the first vsnprintf will modify that
> va_list object to point after all arguments it consumed and when you call
> vsnprintf again with the same va_list (== the same address of some automatic
> struct), it will start looking at garbage.

ah - I've just realised that I missed the requirement for the va_end after the
first vsnprintf() from my reading of the specification - thanks for the
clarification. As with many things, now that it's been pointed out to me, it
does seem  obvious :)

I sit enlightened.
Cheers Jakub

Tim
Comment 5 Jakub Jelinek 2007-08-29 16:40:55 UTC
Last note:
Each invocation of the va_start() and va_copy() macros shall be matched by a
corresponding invocation of the va_end() macro in the same function.
just to be sure.
The current va_end() is fine, all you need to add
va_list ap2;
va_copy (ap2, ap);
before the first vsnprintf call, change second (or first, doesn't matter which
one if just one) vsnprintf call to use ap2 instead and add
va_end (ap2);
after that call.