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 );
}