home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
misc
/
volume5
/
safe-mkdir
< prev
next >
Wrap
Text File
|
1989-02-03
|
12KB
|
448 lines
Path: xanth!nic.MR.NET!hal!ncoast!allbery
From: doug@letni.UUCP (Doug Davis)
Newsgroups: comp.sources.misc
Subject: v05i085: mkdir() and security hole *****FIX****
Summary: how *TO* run a /bin/mkdir
Keywords: mkdir hole fix
Message-ID: <9465@merch.TANDY.COM>
Date: 19 Dec 88 00:24:52 GMT
Sender: allbery@ncoast.UUCP
Reply-To: doug@letni.UUCP (Doug Davis)
Organization: lawnet
Lines: 433
Approved: allbery@ncoast.UUCP
Posting-number: Volume 5, Issue 85
Submitted-by: "Doug Davis" <doug@letni.UUCP>
Archive-name: safe-mkdir
[I looked it over, looks OK from a quick scan -- but is it really secure?
I *think* so, but.... ++bsa]
Attached is a version of /bin/mkdir that should eliminate the
problem with the race condition that someone can take advantage
of to cause a major security hole. Machines that have the
mkdir() function call, most anything based on 4.2 BSD or later,
will not need this program. Everyone else that I know of
should want this.
For security reasons I have elected not to describe the problem with /bin/mkdir
fully, just suffice it to say that I have tested it on 11 differen't
architectures and the "bug" existed on all of them. If your /bin/mkdir
program is setuid root, you too probably have this bug as well.
This mkdir first makes a directory to play in, which is owned by root
and is mode 000. It is made in the same directory in which the
user is requesting his directory. In this "secure" directory, to
which the user allegedly has no access, the mknod(), chown(), and
links for `.' and `..' are performed. The new directory is then linked
into place. Finally, the "secure" directory is removed. Yes, there
is a bit more overhead, but a much more secure program is worth it.
As usual, I will accept mail, suggestions, comments, etc will
be appreciated. Flames will be ignored. If anyone can poke security holes
in this code I would really like to hear about it.
BTW: I know the calls to rand() are not really needed. They just make
it more fun for someone trying to defeat the code.
Doug Davis
--
Lawnet
1030 Pleasent Valley Lane.
Arlington Texas 76015
817-467-3740
{ sys1.tandy.com, motown!sys1, uiucuxc!sys1, killer!texbell } letni!doug
"Talk about holes in UNIX, geeze thats nothing compaired with the security
problems in the ship control programs of StarFleet."
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of shell archive."
# Contents: Makefile mkdir.c
# Wrapped by doug@letni on Thu Dec 15 01:28:50 1988
# { sys1.tandy.com, motown!sys1, uiucuxc!sys1, killer!texbell } letni!doug
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'Makefile' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(510 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X# What kind of strrchr do we have?
X# 'strrchr' for most newer unix's, 'rindex' for earlier editions
X# STTRCHR = -DSTRRCHR=rindex
STRRCHR = -DSTRRCHR=strrchr
X# do you have a rand() function call?
X# If you don't have one, comment out the line below
RAND = -DRAND
X
X# for debugging,
X# -DDEBUG
X
DEFINES = $(STRRCHR) $(DEBUG) $(RAND)
SHELL = /bin/sh
CC = /bin/cc
CFLAGS = -O $(DEFINES)
LDFLAGS = -n -s
X
all: mkdir
X
mkdir.o: mkdir.c
X $(CC) $(CFLAGS) mkdir.c -c
X
mkdir: mkdir.o
X $(CC) $(LDFLAGS) mkdir.o -o mkdir
END_OF_FILE
if test 510 -ne `wc -c <'Makefile'`; then
echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'mkdir.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'mkdir.c'\"
else
echo shar: Extracting \"'mkdir.c'\" \(7645 characters\)
sed "s/^X//" >'mkdir.c' <<'END_OF_FILE'
X/*
X * Secure mkdir program, solves that nasty race problem...
X *
X * 13 December 1988 Doug Davis doug@lenti.lawnet.com
X * and John Elliot IV iv@trsvax.tandy.com
X *
X *
X * Theory of operation:
X * This mkdir first makes a directory to play in, which is
X * owned by root and is mode 000. It is made in the same
X * directory in which the user is requesting his directory.
X * In this "secure" directory, to which the user allegedly
X * has no access, the mknod(), chown(), and links for `.'
X * and `..' are performed. The new directory is then linked
X * into place. Finally, the "secure" directory is removed.
X *
X * This code copyright 1988 by Doug Davis (doug@letni.lawnet.com)
X * You are free to modify, hack, fold, spindle, duplicate, pass-along
X * give-away, publish, transmit, or mutlate this code in any maner,
X * provided that you give credit where credit is due and don't pretend
X * that you wrote it.
X *
X * If you do my lawyers (and I have a lot of lawyers) will teach you a lesson
X * or two in copyright law that you will never ever forget.
X */
X
X#define MAXPATHLEN 128 /* maximum reasonanble path length */
X
X#include <sys/types.h>
X#include <signal.h>
X#include <sys/stat.h>
X#ifdef DEBUG
X# include <stdio.h>
X#else /*DEBUG*/
X# define NULL ((char *) 0)
X#endif /*DEBUG*/
X
X#define MKNODE 1
X#define LINK 2
X
char *Malloc_Failed = "malloc() failed.";
char *Doesnt_Exist = " does not exist.";
char *Cannot_Access = "cannot access ";
char *Already_Exist = " already exists.";
char *Secure_Failed = "makedir secure parent failed ";
char *Couldnt_Link = "Couldn't link to ";
char *Mkdir_Failed = "makedir failed ";
char *Chown_Failed = "chown() failed ";
X
extern char *STRRCHR();
extern char *malloc();
X
extern int errno;
extern int getpid();
X
extern unsigned short getgid();
extern unsigned short getuid();
X
X#ifdef RAND
extern int rand();
X#else /*RAND*/
extern int getppid();
X#endif /*RAND*/
X
extern long time();
X
char *Progname;
X
main(argc, argv)
int argc;
char *argv[];
X{
X Progname = argv[0];
X errno = 0;
X
X if (argc < 2) {
X print("Usage: ");
X print(Progname);
X print(" directory_name [ ... directory_name ]\n");
X exit(0);
X }
X
X /* Catch those nasty signals that could cause us
X * to mess up the filesystem */
X swat_sigs();
X
X while (--argc)
X md(*++argv); /* make each directory */
X
X exit(errno);
X}
X
X
md(s)
char *s;
X{
X char *basename, *parent, *fullname;
X char securename[MAXPATHLEN], securedir[MAXPATHLEN];
X long snum;
X unsigned short myuserid, mygroupid;
X struct stat sanity;
X
X /* find out who I really am */
X myuserid = getuid();
X mygroupid = getgid();
X
X /* set up the pseudo-RANDom number generation system */
X#ifndef RAND
X srand(getpid());
X#endif /*RAND*/
X
X /* see if we are explicit or indirect */
X basename = STRRCHR(s, '/');
X if (basename == (char *) NULL) {
X fullname = malloc(strlen(s)+1);
X if (fullname == (char *) NULL)
X error(Malloc_Failed, NULL, errno);
X parent = malloc(2);
X if (parent == (char *) NULL)
X error(Malloc_Failed, NULL, errno);
X parent[0] = '.';
X parent[1] = '\0';
X strcpy(fullname, s);
X basename = s;
X } else {
X fullname = malloc(strlen(s)+1);
X if (fullname == (char *) NULL)
X error(Malloc_Failed, NULL, errno);
X strcpy(fullname, s);
X *basename = '\0';
X basename++;
X parent = malloc(strlen(s) + 3);
X if (parent == (char *) NULL)
X error(Malloc_Failed, NULL, errno);
X strcpy(parent, s);
X strcat(parent, "/.");
X }
X
X /* Generate the secure names ... */
X do {
X /* round and round we go where we stop depends on
X * the non-existance of securedir */
X snum = time((int *) 0);
X#ifdef RAND
X sprintf(securedir, "%s/%ld", parent, snum - (long)rand());
X sprintf(securename, "%s/%ld", securedir, snum + (long)rand());
X#else /*RAND*/
X sprintf(securedir, "%s/%ld", parent, snum - (long)getppid());
X sprintf(securename, "%s/%ld", securedir, snum + (long)getppid());
X snum += (long)getpid();
X#endif /*RAND*/
X } while (stat(securedir, &sanity) == 0);
X
X#ifdef DEBUG
X /* spill the beans .. */
X printf("parent == %s\n", parent);
X printf("basename == %s\n", basename);
X printf("fullname == %s\n", fullname);
X printf("securedir == %s\n", securedir);
X printf("securename == %s\n", securename);
X fflush(stdout);
X#endif /*DEBUG*/
X
X /* lets see if our parent directory is around... */
X if ((stat(parent, &sanity)) != 0)
X error(parent, Doesnt_Exist, 0);
X
X /* find out if we can write here */
X if (canIwrite(&sanity, myuserid, mygroupid) != 0)
X error(Cannot_Access, parent, 0);
X
X /* find out if we are going to stomp on something.. */
X if ((stat(fullname, &sanity)) == 0)
X error(fullname, Already_Exist, 0);
X
X /* make secure parent directory (note the mode of 0) */
X if (makedir(parent, securedir, 0) > 0)
X error(Secure_Failed, securedir, errno);
X
X /* now make our directory underneath it */
X if (makedir(parent, securename, 0777) > 0)
X error(Mkdir_Failed, securedir, errno);
X
X /* do that eerie little chown() thats the "root" of all our problems */
X if (chown(securename, myuserid, mygroupid) != 0)
X error(Chown_Failed, securename, errno);
X
X /* do a quick sanity check, just to annoy someone trying, unsccessfully
X * I might add, to trick mkdir into chowning something it shouldn't.. */
X if ((stat(fullname, &sanity)) == 0) {
X /* what happend? this wasn't here a couple of functions ago.. */
X unlink(securename);
X rmdir(securedir);
X error(fullname, Already_Exist, 0);
X }
X
X /* okay, put it where it belongs */
X if ((link(securename, fullname)) < 0)
X error(Couldnt_Link, fullname, errno);
X
X /* remove all our rubbish, and tidy everything up.. */
X unlink(securename);
X rmdir(securedir);
X if (parent != (char *) NULL)
X free(parent);
X if (fullname != (char *) NULL)
X free(fullname);
X return(0);
X}
X
makedir(parent, dir, mode)
char *parent, *dir;
int mode;
X{
X char dotdot[MAXPATHLEN];
X
X#ifdef DEBUG
X printf("mkdir(%s, %s)\n", parent, dir);
X fflush(stdout);
X#endif /*DEBUG*/
X
X /* put the node together */
X if ((mknod(dir, S_IFDIR | mode, 0)) < 0)
X return (MKNODE);
X
X /* make dot */
X strcpy(dotdot, dir);
X strcat(dotdot, "/.");
X if ((link(dir, dotdot)) < 0)
X return (LINK);
X
X /* make dotdot */
X strcat(dotdot, ".");
X if ((link(parent, dotdot)) < 0)
X return (LINK);
X
X return (0);
X}
X
rmdir(dir)
char *dir;
X{
X char dots[MAXPATHLEN];
X
X#ifdef DEBUG
X printf("rmdir(%s)\n", dir);
X fflush(stdout);
X#endif /*DEBUG*/
X
X strcpy(dots, dir);
X strcat(dots, "/.");
X
X /* unlink(".") */
X if (unlink(dots) < 0)
X return (LINK);
X
X /* unlink("..") */
X strcat(dots, ".");
X if (unlink(dots) < 0)
X return (LINK);
X
X /* unlink the directory itself */
X if (unlink(dir) < 0)
X return (LINK);
X
X return (0);
X}
X
print(s)
char *s;
X{
X write(2, s, strlen(s));
X}
X
error(s1, s2, err)
char *s1, *s2;
int err;
X{
X write(2, Progname, strlen(Progname));
X write(2, ": ", 2);
X write(2, s1, strlen(s1));
X errno = err;
X if (s2 != NULL)
X write(2, s2, strlen(s2));
X if (err != 0)
X perror(" ");
X else
X write(2, "\n", 1);
X exit(errno);
X}
swat_sigs()
X{
X register int i;
X
X for (i=SIGHUP; i<=NSIG ; i++)
X signal(i, SIG_IGN); /* bye-bye */
X}
canIwrite(stbuff, uid, gid)
register struct stat *stbuff;
register unsigned short uid, gid;
X{
X /* we let root get away with anything... */
X if (uid == 0)
X return(0);
X
X /* can I write in it as an OWNER ? */
X if (uid == stbuff->st_uid && stbuff->st_mode & 0200)
X return(0);
X
X /* okay, so how about as a GROUP ? */
X if (gid == stbuff->st_gid && stbuff->st_mode & 0020)
X return(0);
X
X /* alright, how about an OTHER ? */
X if (stbuff->st_mode & 0002)
X return(0);
X
X /* okay, so I can't write here.. */
X return(-1);
X}
X#ifdef DEBUG
unlink(s)
char *s;
X{
X printf("Unlink(%s)\n", s);
X fflush(stdout);
X}
X#endif /*DEBUG*/
END_OF_FILE
if test 7645 -ne `wc -c <'mkdir.c'`; then
echo shar: \"'mkdir.c'\" unpacked with wrong size!
fi
chmod +x 'mkdir.c'
# end of 'mkdir.c'
fi
echo shar: End of shell archive.
exit 0