bin/60533: 4.9-STABLE libc locale support might contain buffer overflows (or stack corruption, or double free() problem), appearing on some configurations; fix and testcase attached

Michal Pasternak dotz at irc.pl
Tue Dec 23 17:30:15 PST 2003


>Number:         60533
>Category:       bin
>Synopsis:       4.9-STABLE libc locale support might contain buffer overflows (or stack corruption, or double free() problem), appearing on some configurations; fix and testcase attached
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Dec 23 17:30:10 PST 2003
>Closed-Date:
>Last-Modified:
>Originator:     Michal Pasternak
>Release:        4.9-STABLE
>Organization:
>Environment:
FreeBSD mainframe.w.lub.pl 4.9-STABLE FreeBSD 4.9-STABLE #0: Tue Dec 23 20:45:53 CET 2003     root at mainframe.w.lub.pl:/usr/obj/usr/src/sys/MP-UX  i386

>Description:
*****************************************************************

Disclaimer: I might be totally wrong here, but this case is worth
looking at anyway. I have only partial knowledge about gcc, stack
frames, buffer overflows and such stuff.

*****************************************************************


      After a few days of uptime, my fresh install of 4.9-RELEASE (from 
mini-iso) started to behave in a strange way. Whenever I used LC_ALL, 
LC_MESSAGES or LC_NUMERIC environment variables (they worked before!), standard FreeBSD programs from basesystem (like tcsh(1) or ls(1)) dumped 
core while start-up, no matter which locale was used. My RAM is tested; 
coredumps were repeatable; cvsup to -STABLE and buildworld/installworld 
performed a few times (CFLAGS=-O) did *not* change the situation. As it 
came out: 

- programs coredumped at line 69 of src/libc/locale/ldpart.c, then, 
  after my patching, around line 125 (sorry, I don't have those 
  backtraces saved, but look at the code -- it just asks for a coredump, 
  not doing any NULL checking)

- errors appeared, whenever __part_load_locale was called from:

     src/libc/locale files: lmessages.c, lnumeric.c and lmonetary.c.

     src/libc/stdtime/timelocale.c

- errors appeared only with LC_ALL (or LC_* variables set to something 
  else, than "" or "C")


Below is the last gdb backtrace I took before squishing this bug:

Core was generated by `ls'.
Program terminated with signal 11, Segmentation fault.
#0  0x8073551 in __part_load_locale (name=0x80a7860 "en_US.ISO8859-1", 
    using_locale=0x80a9128, locale_buf=0x0, 
    category_filename=0x809f153 "LC_TIME", locale_buf_size_max=58, 
    locale_buf_size_min=58, dst_localebuf=0x80a9040)
    at /usr/src/lib/libc/../libc/locale/ldpart.c:136
136             *locale_buf = lbuf;
(gdb) backtrace
#0  0x8073551 in __part_load_locale (name=0x80a7860 "en_US.ISO8859-1", 
    using_locale=0x80a9128, locale_buf=0x0, 
    category_filename=0x809f153 "LC_TIME", locale_buf_size_max=58, 
    locale_buf_size_min=58, dst_localebuf=0x80a9040)
    at /usr/src/lib/libc/../libc/locale/ldpart.c:136
#1  0x8061d1b in __time_load_locale (name=0x80a7860 "en_US.ISO8859-1")
    at /usr/src/lib/libc/../libc/stdtime/timelocal.c:113
#2  0x805cff4 in loadlocale (category=5)
    at /usr/src/lib/libc/../libc/locale/setlocale.c:317
#3  0x805cc5d in setlocale (category=0, locale=0x80908e9 "")
    at /usr/src/lib/libc/../libc/locale/setlocale.c:205
#4  0x804839c in main (argc=1, argv=0xbfbffb8c) at /usr/src/bin/ls/ls.c:145

The question is: did the bug occur because those programs were 
static-linked? No idea. My pkgsrc-compiled (www.pkgsrc.org)
pkgsrc/editors/jed worked without problems (but hey, it didn't use
any LOCALE settings, I suppose). But -- on the other hand -- 
pkgsrc/databases/postgresql did coredumped.

The just error started to appear and there was no way to stop this. I 
know, sounds silly and unbeliveable. But I have my RAM tested, I have
backtrace, hah -- I even have a patch, which fixed it. Please read on.
>How-To-Repeat:
      This is the best part: I totally have no damn clue, what did I do
on the system.

This problem occurs on my machine on 4-STABLE rebuilt (few times) 
and cvsupped around Tue Dec 23 22:58:01 CET 2003.

Anyway, here is promised testcase. I made it look as similar to code 
found in the files I mentioned in "Full description". Of course this can
not be explictly compared to locale in libc (many factors are different).

Compiling this with AFTER_MY_PATCH set to 0 issues a warning, which is 
not present in warnings generated by libc compile (even with -Wall). 
This might be because of the way __part_load_locale is called.

Remember, that I have not tested if libc enters __part_load_locale
with the same arguments only one time or many times; I have just 
found something, that looks like a serious bug to me -- and created
patches, that get rid of it. I have no more time for testing this
(I have work to do, who hasn't) -- but if you find this situation 
interesting, feel free to e-mail me, I will help with tracking of this
bug to the extent of shell account on my machine.

testcase.c:

#include <stdio.h>
#include <stdlib.h>

/* 
 * AFTER_MY_PATCH:
 * 
 * when defined to "0", segfaults on my setup, which is:
 * FreeBSD mainframe.w.lub.pl 4.9-STABLE FreeBSD 4.9-STABLE #0: Tue Dec 23 20:45:53 CET 2003     root at mainframe.w.lub.pl:/usr/obj/usr/src/sys/MP-UX  i386
 * (a stripped GENERIC)
 * 
 * doc at mainframe:~> gcc --version
 * 2.95.4
 * doc at mainframe:~> ld --version
 * GNU ld version 2.12.1 [FreeBSD] 2002-07-20
 */

#define AFTER_MY_PATCH 1

static char *_whatever_locale_buf;

FAKE__part_load_locale(char **foo) {
   static char *a = "worked";

#if AFTER_MY_PATCH == 1
   puts("here");
   if (_whatever_locale_buf != NULL) {
      puts("and here!");
      if (*_whatever_locale_buf != NULL) {
         puts("still here!");
         if (strcmp("worked", _whatever_locale_buf)==0) {
            puts("I was already used. This is okay.");
            exit(0);
         }
      }
   }
   
   puts("after here!");
#else
   puts("before checking");
   if (*_whatever_locale_buf != NULL && strcmp("worked", *_whatever_locale_buf)==0) {
     puts("I was already used. This is okay.");
     exit(0);
   }
   puts("after checking");
#endif
   
   puts("about to set variable");
   *foo = a;
   puts("did it! returning!");
}

main(){
   FAKE__part_load_locale(&_whatever_locale_buf); 
   puts(_whatever_locale_buf); /* should output "worked" */
   FAKE__part_load_locale(&_whatever_locale_buf); /* should output "already used" and exit */
}

>Fix:
I have only partial knowledge about the calling proces in ldpart.c, so 
those patches might be only a walk-around and not a fix. Please remember
about it.


diff -ur src/lib/libc/stdtime.orig/timelocal.c src/lib/libc/stdtime/timelocal.c
--- src/lib/libc/stdtime.orig/timelocal.c       Wed Dec 24 00:42:32 2003
+++ src/lib/libc/stdtime/timelocal.c    Wed Dec 24 00:42:53 2003
@@ -35,7 +35,7 @@
 
 static struct lc_time_T _time_locale;
 static int _time_using_locale;
-static char *time_locale_buf;
+static char *time_locale_buf = NULL;
 
 #define LCTIME_SIZE (sizeof(struct lc_time_T) / sizeof(char *))
 
@@ -111,7 +111,7 @@
 __time_load_locale(const char *name)
 {
        return (__part_load_locale(name, &_time_using_locale,
-                       time_locale_buf, "LC_TIME",
+                       &time_locale_buf, "LC_TIME",
                        LCTIME_SIZE, LCTIME_SIZE,
                        (const char **)&_time_locale));
 }
diff -ur src/lib/libc/locale.orig/ldpart.c src/lib/libc/locale/ldpart.c
--- src/lib/libc/locale.orig/ldpart.c   Tue Dec 23 23:53:32 2003
+++ src/lib/libc/locale/ldpart.c        Wed Dec 24 00:29:58 2003
@@ -66,9 +66,15 @@
        /*
         * If the locale name is the same as our cache, use the cache.
         */
-       if (*locale_buf != NULL && strcmp(name, *locale_buf) == 0) {
-               *using_locale = 1;
-               return (_LDP_CACHE);
+        if (locale_buf != NULL) {      
+               if (*locale_buf != NULL) {
+                       if (strcmp(name, locale_buf) == 0) {
+                               *using_locale = 1;
+                               return (_LDP_CACHE);
+                       }
+                  
+               }
+          
        }
 
        /*
@@ -121,8 +127,12 @@
        /*
         * Record the successful parse in the cache.
         */
-       if (*locale_buf != NULL)
-               free(*locale_buf);
+        if (locale_buf != NULL) {
+               if (*locale_buf != NULL) {
+                       free(*locale_buf);
+                       *locale_buf = NULL;
+               }
+       }
        *locale_buf = lbuf;
        for (p = *locale_buf, i = 0; i < num_lines; i++)
                dst_localebuf[i] = (p += strlen(p) + 1);
diff -ur src/lib/libc/locale.orig/lmessages.c src/lib/libc/locale/lmessages.c
--- src/lib/libc/locale.orig/lmessages.c        Tue Dec 23 23:53:32 2003
+++ src/lib/libc/locale/lmessages.c     Tue Dec 23 23:56:30 2003
@@ -28,7 +28,7 @@
 __FBSDID("$FreeBSD: src/lib/libc/locale/lmessages.c,v 1.9.2.2 2002/08/12 11:17:37 ache Exp $");
 
 #include <stddef.h>
-
+#include <stdlib.h>
 #include "lmessages.h"
 #include "ldpart.h"
 
@@ -47,7 +47,7 @@
 
 static struct lc_messages_T _messages_locale;
 static int     _messages_using_locale;
-static char    *_messages_locale_buf;
+static char    *_messages_locale_buf = NULL;
 
 int
 __messages_load_locale(const char *name)
@@ -55,7 +55,7 @@
        int ret;
 
        ret = __part_load_locale(name, &_messages_using_locale,
-                 _messages_locale_buf, "LC_MESSAGES",
+                 &_messages_locale_buf, "LC_MESSAGES",
                  LCMESSAGES_SIZE_FULL, LCMESSAGES_SIZE_MIN,
                  (const char **)&_messages_locale);
        if (ret == _LDP_LOADED) {
diff -ur src/lib/libc/locale.orig/lmonetary.c src/lib/libc/locale/lmonetary.c
--- src/lib/libc/locale.orig/lmonetary.c        Tue Dec 23 23:53:32 2003
+++ src/lib/libc/locale/lmonetary.c     Wed Dec 24 00:10:09 2003
@@ -60,7 +60,7 @@
 
 static struct lc_monetary_T _monetary_locale;
 static int     _monetary_using_locale;
-static char    *_monetary_locale_buf;
+static char    *_monetary_locale_buf = NULL;
 
 static char
 cnv(const char *str)
@@ -78,7 +78,7 @@
        int ret;
 
        ret = __part_load_locale(name, &_monetary_using_locale,
-               _monetary_locale_buf, "LC_MONETARY",
+               &_monetary_locale_buf, "LC_MONETARY",
                LCMONETARY_SIZE, LCMONETARY_SIZE,
                (const char **)&_monetary_locale);
        if (ret != _LDP_ERROR)
diff -ur src/lib/libc/locale.orig/lnumeric.c src/lib/libc/locale/lnumeric.c
--- src/lib/libc/locale.orig/lnumeric.c Tue Dec 23 23:53:32 2003
+++ src/lib/libc/locale/lnumeric.c      Tue Dec 23 23:56:21 2003
@@ -28,6 +28,7 @@
 __FBSDID("$FreeBSD: src/lib/libc/locale/lnumeric.c,v 1.10.2.2 2002/08/12 11:17:38 ache Exp $");
 
 #include <limits.h>
+#include <stdlib.h>
 #include "lnumeric.h"
 #include "ldpart.h"
 
@@ -46,7 +47,7 @@
 
 static struct lc_numeric_T _numeric_locale;
 static int     _numeric_using_locale;
-static char    *_numeric_locale_buf;
+static char    *_numeric_locale_buf = NULL;
 
 int
 __numeric_load_locale(const char *name)
@@ -54,7 +55,7 @@
        int ret;
 
        ret = __part_load_locale(name, &_numeric_using_locale,
-               _numeric_locale_buf, "LC_NUMERIC",
+               &_numeric_locale_buf, "LC_NUMERIC",
                LCNUMERIC_SIZE, LCNUMERIC_SIZE,
                (const char **)&_numeric_locale);
        if (ret != _LDP_ERROR)

>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list