This is the mail archive of the libc-alpha@sources.redhat.com mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Possible bug in wcsxfrm() with included testcase


Hi.

There has been a thread recently regarding memory corruption in
glib-2.0. The thread progressed to where the use of __STDC_ISO_10646__ 
in glib/gunicollate.c was the possible root cause of the problems.
I was seeing crashes in various GTK/GNOME applications that I had traced
to the same file. I believe I've created a test case that shows the true
bug is the wcsxfrm() function in glibc. The function is passed a pointer
and scribbles on memory ahead of the pointer.

The test code duplicates the condition that always crashed on my
machine. When opening the 'File Open' dialog the application (usually
eog) would hang once this dialog was closed. Using strace and
MALLOC_CHECK_=2 I was able to find that things were crashing in glib
when the file names were being processed, so my test case opens a
directory, reads the file names, then calls wcsxfrm() as is done in
glib/gunicollate.c.

As I was writing this I was testing it with electric fence and
MALLOC_CHECK_=2. Up until the wcsxfrm() calls were added it ran without
problems so I believe the memory corruption really is in glibc. The
various printf() statements were added so I could see what filename was
being read in and to give an idea of where the program was.

$ cat wcsxfrm_test.c
/* test case attempt for wcsxfrm() */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <wctype.h>
#include <dirent.h>
#include <locale.h>

/* this function comes from glibc info pages */
wchar_t *
mbstouwcs (const char *s)
{
  size_t len;
  wchar_t *result;
  wchar_t *wcp;
  wchar_t tmp[1];
  mbstate_t state;
  size_t nbytes;
  
  len = strlen(s);
  result = malloc((len + 1) * sizeof(wchar_t));
  if (result == NULL) {
    return NULL;
  }
  wcp = result;
  memset (&state, '\0', sizeof (state));
  while ((nbytes = mbrtowc (tmp, s, len, &state)) > 0)
    {
      if (nbytes >= (size_t) -2)
	/* Invalid input string.  */
	return NULL;
      *result++ = towupper (tmp[0]);
      len -= nbytes;
      s += nbytes;
    }
  return result;
}

int
main(int argc, char ** argv) {
  DIR * dir;
  struct dirent * dentry;
  size_t namelen;
  size_t xfrm_len;
  char * cp = NULL;
  wchar_t * wcstr = NULL;
  wchar_t * result_wc = NULL;

  if (argc < 2) {
    printf("Pass a directory name!\n");
    return EXIT_FAILURE;
  }
  dir = opendir(argv[1]);
  if (dir == NULL) {
    printf("opendir() failed on '%s'\n", argv[1]);
    return EXIT_FAILURE;
  }
  setlocale (LC_COLLATE, ""); /* done in glib/gunicollate.c */
  while ((dentry = readdir(dir)) != NULL)
    {
      printf("entry name: '%s'\n", dentry->d_name);
      namelen = strlen(dentry->d_name);
      cp = malloc(namelen + 1);
      if (cp == NULL) {
	printf("malloc() failure\n");
	closedir(dir);
	return EXIT_FAILURE;
      }
      strcpy(cp, dentry->d_name);
      wcstr = mbstouwcs(cp);
      if (wcstr == NULL) {
	printf("mbstouwcs() failure\n");
	free(cp);
	closedir(dir);
	return EXIT_FAILURE;
      }
      /* This part is like what is done in gunicollate.c */
      xfrm_len = wcsxfrm(NULL, wcstr, 0);
      printf("xfrm_len: %zd\n", xfrm_len);
      result_wc = malloc((xfrm_len + 1) * sizeof(wchar_t));
      if (result_wc == NULL) {
	printf("malloc() failure\n");
	free(wcstr);
	free(cp);
	closedir(dir);
	return EXIT_FAILURE;
      }
      wcsxfrm(result_wc, wcstr, xfrm_len + 1); /* BOOM! */
      free(result_wc);
      free(wcstr);
      free(cp);
    }
  closedir(dir);
  return EXIT_SUCCESS;
}
$ gcc -g -O2 -Wall -o wcsxfrm_test wcsxfrm_test.c 
$ EF_PROTECT_BELOW=1 ef ./wcsxfrm_test /tmp

  Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
entry name: '.'
xfrm_len: 0
/opt/gnu/bin/ef: line 20: 17716 Segmentation fault      (core dumped)
( export LD_PRELOAD=/opt/gnu/lib/libefence.so.0.0; exec $* )
$ gdb ./wcsxfrm_test core
#0  0x4009d09e in wcpncpy () from /lib/libc.so.6
(gdb) bt
#0  0x4009d09e in wcpncpy () from /lib/libc.so.6
#1  0x400ae0ee in wcsxfrm () from /lib/libc.so.6
#2  0x08048785 in main (argc=2, argv=0x401a2000) at wcsxfrm_test.c:89
(gdb) frame 2
#2  0x08048785 in main (argc=2, argv=0x401a2000) at wcsxfrm_test.c:89
89            wcsxfrm(result_wc, wcstr, xfrm_len + 1); /* BOOM! */
$

I'm currently using libc-2.3.2 from Debian - the changelog with this
package shows it is current up to July 17. I'd mailed this test case to
Martin Schlemmer and he said it crashed on his machine as well, so maybe
this is the root of the problem he was seeing.

I believe the test case is valid and shows there is a bug in wcsxfrm(),
or more precisely wcpncpy(). Agree?

Art Haas

-- 
Man once surrendering his reason, has no remaining guard against absurdities
the most monstrous, and like a ship without rudder, is the sport of every wind.

-Thomas Jefferson to James Smith, 1822


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]