A security hole
Doug Gwyn
gwyn at brl-smoke.ARPA
Sun Mar 20 11:30:47 AEST 1988
In article <319 at wsccs.UUCP> terry at wsccs.UUCP (terry) writes:
>In article <478 at minya.UUCP>, jc at minya.UUCP (John Chambers) writes:
>} In article <722 at rivm05.UUCP>, ccement at rivm.UUCP (Martien F v Steenbergen) writes:
>} In article <181 at wsccs.UUCP>, I write:
>} > Do NOT write a setuid program that uses getcwd(). The getcwd() call
>} > does a popen() of the "pwd" shell command and does not check it's path.
> Read the source code. I was simply pointing out something you should
>be aware of. The fix, if you haven't figured it out for yourself yet, is to
>simply force the path for pwd. I was simply suggesting that AT&T fix it.
Enough, already. Here's an alternative solution:
/*
getcwd -- get current working directory name (POSIX and SVID compatible)
last edit: 21-Sep-1987 D A Gwyn
This public-domain getcwd() routine can be used to replace the UNIX
System V library routine (which uses popen() to capture the output of
the "pwd" command). Once that is done, "pwd" can be reimplemented as
just puts(getcwd()).
This implementation depends on every directory having entries for
"." and "..". It also depends on the internals of the <dirent.h>
data structures to some degree.
I considered using chdir() to ascend the hierarchy, followed by a
final chdir() to the path being returned by getcwd() to restore the
location, but decided that error recovery was too difficult that way.
The algorithm I settled on was inspired by my rewrite of the "pwd"
utility, combined with the dotdots[] array trick from the SVR2 shell.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
typedef char *pointer; /* (void *) if you have it */
extern void free();
extern pointer malloc();
extern int fstat(), stat();
extern int errno; /* normally done by <errno.h> */
#ifndef NULL
#define NULL 0 /* amorphous null pointer constant */
#endif
#ifndef NAME_MAX
#define NAME_MAX 255 /* maximum directory entry size */
#endif
char *
getcwd( buf, size ) /* returns pointer to CWD pathname */
char *buf; /* where to put name (NULL to malloc) */
int size; /* size of buf[] or malloc()ed memory */
{
static char dotdots[] =
"../../../../../../../../../../../../../../../../../../../../../../../../../..";
char *dotdot; /* -> dotdots[.], right to left */
DIR *dirp; /* -> parent directory stream */
struct dirent *dir; /* -> directory entry */
struct stat stat1, stat2; /* info from stat() */
struct stat *d = &stat1; /* -> info about "." */
struct stat *dd = &stat2; /* -> info about ".." */
register char *buffer; /* local copy of buf, or malloc()ed */
char *bufend; /* -> buffer[size] */
register char *endp; /* -> end of reversed string */
register char *dname; /* entry name ("" for root) */
int serrno = errno; /* save entry errno */
if ( size == 0 )
{
errno = EINVAL; /* invalid argument */
return NULL;
}
if ( (buffer = buf) == NULL /* wants us to malloc() the string */
&& (buffer = (char *)malloc( (unsigned)size )) == NULL
) {
errno = ENOMEM; /* cannot malloc() specified size */
return NULL;
}
if ( stat( ".", dd ) != 0 ) /* prime the pump */
goto error; /* errno already set */
endp = buffer; /* initially, empty string */
bufend = &buffer[size];
for ( dotdot = &dotdots[sizeof(dotdots)]; dotdot != dotdots; )
{
dotdot -= 3; /* include one more "/.." section */
/* (first time is actually "..") */
/* swap stat() info buffers */
{
register struct stat *temp = d;
d = dd; /* new current dir is old parent dir */
dd = temp;
}
if ( (dirp = opendir( dotdot )) == NULL ) /* new parent */
goto error; /* errno already set */
if ( fstat( dirp->dd_fd, dd ) != 0 )
{
serrno = errno; /* set by fstat() */
(void)closedir( dirp );
errno = serrno; /* in case closedir() clobbered it */
goto error;
}
if ( d->st_dev == dd->st_dev )
{ /* not crossing a mount point */
if ( d->st_ino == dd->st_ino )
{ /* root directory */
dname = "";
goto append;
}
do
if ( (dir = readdir( dirp )) == NULL )
{
(void)closedir( dirp );
errno = ENOENT; /* missing entry */
goto error;
}
while ( dir->d_ino != d->st_ino );
}
else { /* crossing a mount point */
struct stat t; /* info re. test entry */
char name[sizeof(dotdots) + 1 + NAME_MAX];
(void)strcpy( name, dotdot );
dname = &name[strlen( name )];
*dname++ = '/';
do {
if ( (dir = readdir( dirp )) == NULL )
{
(void)closedir( dirp );
errno = ENOENT; /* missing entry */
goto error;
}
(void)strcpy( dname, dir->d_name );
/* must fit if NAME_MAX is not a lie */
}
while ( stat( name, &t ) != 0
|| t.st_ino != d->st_ino
|| t.st_dev != d->st_dev
);
}
dname = dir->d_name;
/* append "/" and reversed dname string onto buffer */
append:
if ( endp != buffer /* avoid trailing / in final name */
|| dname[0] == '\0' /* but allow "/" when CWD is root */
)
*endp++ = '/';
{
register char *app; /* traverses dname string */
for ( app = dname; *app != '\0'; ++app )
;
if ( app - dname >= bufend - endp )
{
(void)closedir( dirp );
errno = ERANGE; /* won't fit allotted space */
goto error;
}
while ( app != dname )
*endp++ = *--app;
}
(void)closedir( dirp );
if ( dname[0] == '\0' ) /* reached root; wrap it up */
{
register char *startp; /* -> buffer[.] */
*endp = '\0'; /* plant null terminator */
/* straighten out reversed pathname string */
for ( startp = buffer; --endp > startp; ++startp )
{
char temp = *endp;
*endp = *startp;
*startp = temp;
}
errno = serrno; /* restore entry errno */
return buffer;
}
}
errno = ENOMEM; /* actually, algorithm failure */
error:
if ( buf == NULL )
free( (pointer)buffer );
return NULL;
}
More information about the Comp.bugs.sys5
mailing list