COMMAND

    X11R6.1 (and earlier)

SYSTEMS AFFECTED

    Any system which is running  X11R6.1 or earlier, and has  at least
    one setuid or setgid program which uses libX11 is vulnerable.

PROBLEM

    Following text  is Secure  Network Inc  (SNI) copyright  and it is
    part  of  their  security  advisory  about  environment   variable
    problems in X11.

    While examining  the differences  between X11R6.1  and X11R6.3, it
    has  come  to  our  attention  that  a  number of serious security
    problems in libX11  were fixed between  releases.  These  problems
    permit  unprivileged  users  to  obtain elevated access, including
    group  sys,  group  kmem,  and  root  privileges, depending on the
    operating system and  the X11 release.   Administrators should  be
    aware  that  these  problems  are  actively  being  exploited, and
    should take the precautions outlined below to ensure they are  not
    susceptible to these problems.


    Technical Details
    ~~~~~~~~~~~~~~~~~
    In X11R6.1 and earlier, there  are many places where libX11  looks
    at environment variables, and  then performs string operations  on
    them.  X11R6.1  and earlier, however,  perform no bounds  checking
    when doing these  string operations.   Setuid and setgid  programs
    which use functions provided by  libX11 may allow users to  obtain
    elevated privileges.

    One of the many examples of flawed code in X11R6.1, in this case
    from GetDflt.c reads:

        if (ptr = getenv("HOME"))
                (void) strcpy(dest, ptr);

    While the corrected code  for this particular exammple  in X11R6.3
    reads:

        if (ptr = getenv("HOME")) {
                (void) strncpy(dest, ptr, len);
                dest[len-1] = '\0';

    Note that this code correctly adds a null character at the end  of
    the string after the strncpy.


    Impact
    ~~~~~~
    Depending  on  platform  and  X11  release, individuals with shell
    access  can  obtain  elevated  access,  including group sys, group
    kmem, and root privileges.


    Vulnerable Systems
    ~~~~~~~~~~~~~~~~~~
    You can perform a simple test to determine whether your system  is
    vulnerable.  First, set the HOME environment variable to a  string
    at least 2500  characters long.   Using a sh  compatible shell, do
    this by issuing the commands:

        $ HOME=jjjjjjj...jjjjj                  (2500 repititions of 'j')
        $ export HOME

    Using csh or tcsh, use the command:

        % setenv HOME jjjjj...jjjjjjj           (2500 repititions of 'j')

    Then, run a  setuid or setgid  X program, such  as xload.   If you
    are running  a vulnerable  release of  X11, you  will get an error
    message including  either the  words "Segmentation  Fault" or "Bus
    error."  If the words "Segmentation Fault" and "Bus Error" do  not
    appear,  and  the   program  operates  correctly,   you  are   not
    vulnerable to this problem.

    Be  aware  that  if  you  use  a  string  much  shorter  than 2500
    characters, this test will not produce meaningful results, because
    the length of the buffer in question is 2048 characters.  Also, if
    your  DISPLAY  environment  variable  does  not point to a display
    which you have authorization to  connect to, the test will  not be
    able to connect to a valid display and therefore will not work.

    If you have any questions about SNI advisory, feel free to contact
    David Sacerdote, at davids@secnet.com.  If you should wish to
    encrypt your message, check SNI SA for his PGP public key

SOLUTION

    To fix these  problems without loss  of functionality, upgrade  to
    the current release of X11.   You can obtain X11R6.3 by  referring
    to http://www.x.org/consortium/GettingX.html

    As an  alternative workaround,  administrators may  want to remove
    setuid and  setgid bits  from vulnerable  programs.   To find  all
    setuid and setgid programs, in the X11 distribution, the following
    command can be executed:

        % cd /usr/X11/bin
        % find . \( -perm -02000 -o -perm -04000 \) -exec ls -l {} \;
        % find . \( -perm -02000 -o -perm -04000 \) -exec chmod ug-s {} \;

    Remember  to  perform  the  same  command  if  you  wish to remove
    permissions  from  programs  stored  in  other system directories.
    Keep in mind that that the  use of this workaround will result  in
    reduced functionality for non-root users.

    Pail Szabo  wrote following  wrapper, and  used it  to wrap xload,
    xterm and xconsole on Digital Unix 4.0 (only tested under this).

    /*
        sec_wrapper.c -- Wrap setuid  program to prevent command  line
                         or environment variable buffer overrun.

        Only tested on DUnix V4.0

        Does NOT check the values of the arguments and/or  environment
        (other than checking their lengths), though that functionality
        could (should?) be added.

        This program is based loosely on the lpr wrapper released with
        AUSCERT Advisory AA-96.12. Any errors are my own.

        DISCLAIMER:     Anything you do with this is at your own risk.

            V1.0  26 Feb 97  PSz

            Installation instructions
            ~~~~~~~~~~~~~~~~~~~~~~~~~

            1.    su to root

            2.    Determine  the full  path of  the prog  you want  to
                  wrap, and its permissions, owner, and group:

                      # ls -lg /usr/bin/badprog

            3.    Copy  badprog   to  badprog.real,  and  change   the
                  permissions on it:

                      # cd /usr/bin
                      # cp -ip badprog badprog.real
                      # chmod 711 badprog.real

            4.    Edit (a  copy of) this program and define  REAL_PROG
                  (an absolute pathname),  check the settings  of MAX_
                  and GOOD_ variables, then compile:

                      # cc -o badprog badprog_wrapper.c

            5.    Copy  this new  wrapper program  into the  directory
                  originally   containing   badprog,   replacing   the
                  existing badprog program.

                  Use the information found in step #2 and set the same
                  owner, group, permissions on the new badprog program:

                      # cp badprog /usr/bin
                      # cd /usr/bin
                      # chown root badprog
                      # chgrp daemon badprog
                      # chmod 6711 badprog

                  Check that the permissions, owner and group  exactly
                  match those noted in step #2:

                      # ls -lg /usr/bin/badprog

            6.    Check that badprog still works!
*/


/* Make sure REAL_PROG points to the right location */
/* The MAX_ values should be appropriate to the program being wrapped. */
/* The GOOD_ values should be appropriate, leave undefined to skip check. */
/* (Beware: $TERMCAP often contains weird characters.) */

/* #define REAL_PROG    "/usr/bin/badprog.real" */

#define MAX_ARGC        50
#define MAX_ARG_LENGTH  1000
#define MAX_ENV_LENGTH  1000
/* #define GOOD_ARG_CHARS       "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/_.-" */
/* #define GOOD_ENV_CHARS       "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/_.,:+-=" */


#define SYSLOG 1


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef SYSLOG
#include <syslog.h>
#endif


#ifdef SYSLOG
#define sysl(msg)       syslog(LOG_ERR,"Possible %s attack by uid %d: %s\n",REAL_PROG,getuid(),msg)
#else
#define sysl(msg)
#endif

#define ZAP(x,err)      if (x) { printf("Sorry: %s\n",err); sysl(err); exit(-1); }


#ifndef REAL_PROG
        Cannot compile: you do not have REAL_PROG defined
#endif
#if MAX_ARGC > 1000 || MAX_ARGC < 1
        Cannot compile: you have a crazy value for MAX_ARGC
#endif
#if MAX_ARG_LENGTH > BUFSIZ || MAX_ARG_LENGTH < 10
        Cannot compile: you have a crazy value for MAX_ARG_LENGTH
#endif
#if MAX_ENV_LENGTH > BUFSIZ || MAX_ENV_LENGTH < 10
        Cannot compile: you have a crazy value for MAX_ENV_LENGTH
#endif


void main( int argc, char* argv[] /* , char *envp[] */ )
{
        int     iii, jjj;
        char    **ccc, *ddd;

        extern char **environ;

        ZAP( argc<1, "too few arguments" );
        ZAP( argc>MAX_ARGC, "too many arguments" );

        for( iii=1; iii<argc; iii++ )
        {
                jjj = strlen(argv[iii]);
                ZAP( jjj<1, "argument too short" );
                ZAP( jjj>MAX_ARG_LENGTH, "argument too long" );
#ifdef GOOD_ARG_CHARS
                ZAP( strspn(argv[iii],GOOD_ARG_CHARS) != jjj, "bad char in argument" );
#endif
        }

        /* Which one: environ and/or envp ?? */
        for( ccc = environ; *ccc; ccc++ )
        {
                jjj = strlen(*ccc);
                ZAP( jjj<2, "environment component too short" );
                ZAP( jjj>MAX_ENV_LENGTH, "environment component too long" );
                ZAP( !strncmp(*ccc,"LD_",3), "suspicious environment component LD_" );
                ddd = strchr(*ccc,'=');
                ZAP( (int)ddd < (int)*ccc || (int)ddd >= ((int)*ccc)+jjj || *ddd != '=',
                    "badly formed environment component" );
#ifdef GOOD_ENV_CHARS
                ZAP( strspn(*ccc,GOOD_ENV_CHARS) != jjj, "bad char in environment component" );
#endif
        }

        /* execve( REAL_PROG, argv, envp ); */
        execv( REAL_PROG, argv );
        perror( REAL_PROG );
        exit( 1 );
}