COMMAND

    catopen() (libc)

SYSTEMS AFFECTED

    FreeBSD 5.0-CURRENT, 4.x and 3.x prior to 2000-09-27

PROBLEM

    Following  is   based  on   FreeBSD-SA-00:53  Security   Advisory.
    catopen() and setlocale() are functions which are used to  display
    text in a localized format, e.g. for international users.

    There are two problems addressed in this advisory:

    1) The  catopen()  function  did  not  correctly  bounds-check  an
       internal buffer  which could  be indirectly  overflowed by  the
       setting of  an environment  variable. A  privileged application
       which uses catopen()  could be made  to execute arbitrary  code
       by an unprivileged local user.

    2) The catopen() and setlocale() functions could be made to use an
       arbitrary file  as the  source for  localized data  and message
       catalogs, instead  of one  of the  system files.   An  attacker
       could create  a file  which is  a valid  locale file or message
       catalog but which contains special formatting characters  which
       may allow certain badly  written privileged applications to  be
       exploited and execute arbitrary code as the privileged user.

    This second vulnerability is  slightly different from the  problem
    originally  discovered  by  Ivan  Arce  of  Core-SDI which affects
    multiple  UNIX  operating  systems,  which  involved  a  different
    environment  variable  and  which  FreeBSD  is not susceptible to.
    However  Vulnerability  2  was  discovered  in  FreeBSD  after the
    publication  the  Core-SDI  advisory,  and  has the same effect on
    vulnerable applications.

    Note that the FreeBSD base system is not believed to be vulnerable
    to either of  these problems, nor  are any vulnerable  third party
    programs  (including  FreeBSD  ports)  currently known.  Therefore
    the impact on  the majority of  FreeBSD systems is  expected to be
    nonexistent.

SOLUTION

    Vulnerability 1 described  above is the  more serious of  the two,
    since it  does not  require the  application to  contain a  coding
    flaw in order to  exploit it.  A  scanning utility is provided  to
    detect privileged binaries which use the catopen() function  (both
    statically  and  dynamically  linked  binaries),  which  should be
    either  rebuilt,  or  have  their  privileges  limited to minimize
    potential risk.  It is  not feasible to detect binaries  which are
    vulnerable  to  the  second  vulnerability,  however  the provided
    utility will also report statically linked binaries which use  the
    setlocale() functions and  which *may* potentially  be vulnerable.
    Most of the binaries reported will not in fact be vulnerable,  but
    should be  recompiled anyway  for maximum  assurance of  security.
    Note  that  some  FreeBSD  system  binaries  may  be  reported  as
    possibly vulnerable by this script, however this is not the case.

    Statically linked binaries which  are identified as vulnerable  or
    potentially vulnerable should be recompiled from source code after
    patching and recompiling  libc, if possible,  in order to  correct
    the vulnerability.  Dynamically linked binaries will be  corrected
    by simply patching and recompiling libc as described below.

    As an interim measure, consider removing any identified setuid  or
    setgid binary,  removing set[ug]id  privileges from  the file,  or
    limiting the file access permissions, as appropriate.

    Of course, it is possible that some of the identified files may be
    required for the correct operation of your local system, in  which
    case there is no clear  workaround except for limiting the  set of
    users who  may run  the binaries,  by an  appropriate use  of user
    groups and removing the "o+x" file permission bit.

    1) Download the 'scan_locale.sh' and 'test_locale.sh' scripts from

        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/tools/SA-00:53/scan_locale.sh
        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/tools/SA-00:53/test_locale.sh

    2) Verify the md5 checksums and compare to the value below:

        # /sbin/md5 scan_locale.sh
        MD5 (scan_locale.sh) = efea80f74b05e7ddbc0261ef5211e453
        # /sbin/md5 test_locale.sh
        MD5 (test_locale.sh) = 2a485bf8171cc984dbc58b4d545668b4

    3) Run the scan_locale.sh script against your system:

        # sh scan_locale.sh ./test_locale.sh /

       This will scan your entire system for setuid or setgid binaries
       which make use of  the  exploitable  function catopen(), or the
       potentially exploitable function setlocale().   Each   returned
       binary should be examined (eg. with 'ls -l' and/or other tools)
       to determine what  security  risk  it   poses  to  your   local
       environment, eg. whether it can be run by arbitrary local users
       who may be able to exploit it to gain privileges.

       Note  that  this  script   reports   setlocale()   usage  (i.e.
       vulnerability 2) only  in   statically  linked   binaries,  not
       dynamically linked binaries, because of the high rate  of false
       positives.  It is likely that the majority  of such setlocale()
       binaries identified are  not insecure and  their identification
       by this script should not be taken as evidence  that  they  are
       vulnerable, but they should  be  recompiled  anyway for maximum
       assurance of security.

    4) Remove  the  binaries,  or  reduce  their file permissions,  as
       appropriate.

    Upgrade your vulnerable FreeBSD system to 4.1-STABLE or 3.5-STABLE
    after the  correction date,  or patch  your present  system source
    code  and  rebuild.   Then   run  the  scan_locale.sh  script   as
    instructed  above  and  identify  any  statically-linked  binaries
    as  reported  by  the  script.   These  should  either be removed,
    recompiled, or have privileges  restricted to secure them  against
    this vulnerability (since  statically-linked binaries will  not be
    affected by simply recompiling the shared libc library).

    To patch your  present system: save  the patch below  into a file,
    and execute the following commands as root:

        cd /usr/src/lib/libc
        patch < /path/to/patch/file
        make all
        make install

    Patches for FreeBSD systems before the correction date:

    Index: msgcat.c
    ===================================================================
    RCS file: /usr2/ncvs//src/lib/libc/nls/msgcat.c,v
    retrieving revision 1.21
    retrieving revision 1.27
    diff -u -r1.21 -r1.27
    --- nls/msgcat.c	2000/01/27 23:06:33	1.21
    +++ nls/msgcat.c	2000/09/01 11:56:31	1.27
    @@ -91,8 +91,9 @@
         __const char *catpath = NULL;
         char        *nlspath;
         char	*lang;
    -    long	len;
         char	*base, *cptr, *pathP;
    +    int		spcleft;
    +    long	len;
         struct stat	sbuf;
    
         if (!name || !*name) {
    @@ -106,10 +107,10 @@
         } else {
 	    if (type == NL_CAT_LOCALE)
 		    lang = setlocale(LC_MESSAGES, NULL);
    -	else {
    -		if ((lang = (char *) getenv("LANG")) == NULL)
    -			lang = "C";
    -	}
    +	else
    +		lang = getenv("LANG");
    +	if (lang == NULL || strchr(lang, '/') != NULL)
    +		lang = "C";
 	    if ((nlspath = (char *) getenv("NLSPATH")) == NULL
     #ifndef __NETBSD_SYSCALLS
 	        || issetugid()
    @@ -129,13 +130,22 @@
 		    *cptr = '\0';
 		    for (pathP = path; *nlspath; ++nlspath) {
 		        if (*nlspath == '%') {
    +		        spcleft = sizeof(path) - (pathP - path);
 			    if (*(nlspath + 1) == 'L') {
 			        ++nlspath;
    -			    strcpy(pathP, lang);
    +			    if (strlcpy(pathP, lang, spcleft) >= spcleft) {
    +				free(base);
    +				errno = ENAMETOOLONG;
    +				return(NLERR);
    +			    }
 			        pathP += strlen(lang);
 			    } else if (*(nlspath + 1) == 'N') {
 			        ++nlspath;
    -			    strcpy(pathP, name);
    +			    if (strlcpy(pathP, name, spcleft) >= spcleft) {
    +				free(base);
    +			        errno = ENAMETOOLONG;
    +				return(NLERR);
    +			    }
 			        pathP += strlen(name);
 			    } else *(pathP++) = *nlspath;
 		        } else *(pathP++) = *nlspath;
    @@ -186,7 +196,7 @@
         MCSetT	*set;
         long	lo, hi, cur, dir;
    
    -    if (!cat || setId <= 0) return(NULL);
    +    if (cat == NULL || setId <= 0) return(NULL);
    
         lo = 0;
         if (setId - 1 < cat->numSets) {
    @@ -212,8 +222,8 @@
 	    if (hi - lo == 1) cur += dir;
 	    else cur += ((hi - lo) / 2) * dir;
         }
    -    if (set->invalid)
    -	(void) loadSet(cat, set);
    +    if (set->invalid && loadSet(cat, set) <= 0)
    +	return(NULL);
         return(set);
     }
    
    @@ -225,7 +235,7 @@
         MCMsgT	*msg;
         long	lo, hi, cur, dir;
    
    -    if (!set || set->invalid || msgId <= 0) return(NULL);
    +    if (set == NULL || set->invalid || msgId <= 0) return(NULL);
    
         lo = 0;
         if (msgId - 1 < set->numMsgs) {
    @@ -318,7 +328,7 @@
         off_t	nextSet;
    
         cat = (MCCatT *) malloc(sizeof(MCCatT));
    -    if (!cat) return(NLERR);
    +    if (cat == NULL) return(NLERR);
         cat->loadType = MCLoadBySet;
    
         if ((cat->fd = _open(catpath, O_RDONLY)) < 0) {
    @@ -351,7 +361,7 @@
    
         cat->numSets = header.numSets;
         cat->sets = (MCSetT *) malloc(sizeof(MCSetT) * header.numSets);
    -    if (!cat->sets) NOSPACE();
    +    if (cat->sets == NULL) NOSPACE();
    
         nextSet = header.firstSet;
         for (i = 0; i < cat->numSets; ++i) {
    Index: setlocale.c
    ===================================================================
    RCS file: /home/ncvs/src/lib/libc/locale/setlocale.c,v
    retrieving revision 1.27
    retrieving revision 1.28
    diff -u -r1.27 -r1.28
    --- locale/setlocale.c	2000/09/04 03:43:24	1.27
    +++ locale/setlocale.c	2000/09/08 07:29:48	1.28
    @@ -129,7 +129,7 @@
 		    if (!env || !*env)
 			    env = getenv("LANG");
    
    -		if (!env || !*env)
    +		if (!env || !*env || strchr(env, '/'))
 			    env = "C";
    
 		    (void) strncpy(new_categories[category], env, ENCODING_LEN);