[comp.sources.misc] v06i028: New version of secure mkdir

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (01/30/89)

Posting-number: Volume 6, Issue 28
Submitted-by: doug@hal.CWRU.Edu@i.UUCP (Doug Davis at letni.UUCP)
Archive-name: smkdir3

[Still?!  ++bsa]

here is the lastest, and hopefully the last version of my secure mkdir
program, if its possable replace all copys of the previous one with
this one. Of course as soon as all the bugs get are found *AND* reported
there might be another one. ;-)

doug


#! /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.UUCP on Thu Jan 26 20:05:38 1989
# This shar file will self destruct in 4 seconds, good luck %s.
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'\" \(536 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
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,  Does not unlink the secure tree.
X# DEBUG	= -DDEBUG
X
DEFINES	= $(STRRCHR) $(DEBUG) $(RAND)
SHELL	= /bin/sh
CC	= /bin/cc
CFLAGS	= $(DEFINES) -O
LDFLAGS	= -n
X
mkdir: mkdir.o
X	$(CC) mkdir.o $(LDFLAGS) -o mkdir
X
mkdir.o: mkdir.c
X	$(CC) $(CFLAGS) -c mkdir.c
END_OF_FILE
if test 536 -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'\" \(11864 characters\)
sed "s/^X//" >'mkdir.c' <<'END_OF_FILE'
char version[]="mkdir (1.007)";
X
X/*
X * Secure mkdir program, solves that nasty race problem...
X *
X *	13 December 1988	Doug Davis         doug@letni.lawnet.com
X *      	            and John Elliott IV    iv@trsvax.tandy.com
X *
X * Modification History: 
X *  M007   9 Jan 89 Gordon Burditt       gordon@trsvax.tandy.com
X *                  - Fixed the signal handler to properly handle
X *                    previously ignored signals.
X *                  - Fixed canIwrite() to follow SVID conventions.
X *  M006   6 Jan 89 Doug Davis           doug@letni.lawnet.com
X *                  - Fixed M005 so it would not leave the secure
X *                    tree lying around after recipt of multple 
X *                    signals.
X *  M005   5 Jan 89 John Haugh II        jfh@rpp386.dallas.tx.us
X *                  - Added code to abort without createing all of the
X *                    directoies that were specified.
X *  M004  22 Dec 88 Doug Davis           doug@letni.lawnet.com
X *                  - Fixed the error handler for the second makedir() call
X *                    so it will unlink the securedir if it fails.
X *                  - Fixed the error handler for chown() so that
X *                    it removes the working "secure" directories.
X *                    if it ever fails
X *                  - cleaned up some odd tabs in the comments, and ran
X *                    them thru a spell checker.
X *  M003  22 Dec 88 John Elliott IV      iv@trsvax.tandy.com
X *                  - Fixed the spelling of my name
X *                  - General readability cleanup
X *                  - Fixed backwards #ifdef RAND for srand() call
X *                  - Saved some storage by declaring error msgs char msg[]
X *                  - Fixed call order in "cleanup" code so that if the
X *                    unlink() of the securename fails, we will still try
X *                    to remove our scratch directory
X *                  - Some calls to error() had an errno of 0, which meant
X *                    that mkdir would incorrectly exit 0.  Calls to error()
X *                    now include an exit code, which error() now exits with.
X *                  - Fixed case where link-into-place failed and left all
X *                    our flotsam and jetsam around.  Now rmdir() everything
X *                    before we call error().
X *                  - Changed the two error message symbols which began with
X *                    "Couldnt_" to begin with "Cant_" so the symbols will
X *                    be significant in 7 characters, for those so unfortunate
X *                    not to have flexnames.
X *  M002  21 Dec 88 Doug Davis           doug@letni.lawnet.com
X *                  - Cleaned up code, added function declarations
X *                  - fixed bug in second stat's unlink code
X *  M001  19 Dec 88 John Haugh II          jfn@rpp386
X *                  - Fixes oops in original code, much more secure now.
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
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	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 Cant_Link[]	= "Couldn't link to ";
char Mkdir_Failed[]	= "makedir failed ";
char Chown_Failed[]	= "chown() failed ";
char Cant_Unlink[]	= "Couldn't unlink ";
X
extern char *STRRCHR();
extern char *malloc();
X
extern int errno;
extern int getpid();
extern int stat();
extern int chown();
extern int unlink();
extern int strlen();
X
extern unsigned short getuid();
extern unsigned short getgid();
extern long time();
X
X
char *Progname;
int signaled = 0;
X
X#ifdef RAND
X  extern int rand();
X#else /*RAND*/
X  extern int getppid();
X#endif /*RAND*/
X
X/* This function is part of the signal handler, it is used to prevent
X * mkdir from creating all of the directories on the command line, 
X * upon recipt of a signal mkdir will finish the directory that it is
X * currently doing then exit
X */
int
catch()
X{
X	signaled = 1;
X	set_sigs(SIG_IGN);
X}
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	set_sigs(catch);
X
X	while (--argc && ! signaled)
X		md(*++argv); /* make each directory */
X
X	exit(0);
X}
X
X
int
md(s)
char *s;
X{
X	register char *basename,
X                  *parent,
X                  *fullname;
X
X	unsigned short myuserid,
X                   mygroupid;
X
X	char securename[MAXPATHLEN],
X         securedir[MAXPATHLEN],
X         dotsecurename[MAXPATHLEN];
X
X	int	rval1,
X        rval2;
X
X	long snum;
X
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#ifdef 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, (char *)NULL, errno, 1);
X		parent = malloc(2);
X		if (parent == (char *)NULL)
X			error(Malloc_Failed, (char *)NULL, errno, 1);
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, (char *)NULL, errno, 1);
X		strcpy(fullname, s);
X		*basename = '\0';
X		basename++;
X		parent = malloc(strlen(s) + 3);
X		if (parent == (char *)NULL)
X			error(Malloc_Failed, (char *)NULL, errno, 1);
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((long *) 0L);	
X#ifdef RAND
X		sprintf(securedir, "%s/%ld", parent,
X            (rand() % 2) ? snum + (long)rand() : snum - (long)rand());
X		sprintf(securename, "%s/%ld", securedir,
X			(rand() % 2) ? snum - (long)rand() : snum + (long)rand());
X		sprintf(dotsecurename, "%s/./.", securename);
X#else /*RAND*/
X		sprintf(securedir, "%s/%ld", parent, snum - (long)getppid());
X		sprintf(securename, "%s/%ld", securedir, snum + (long)getppid());
X		sprintf(dotsecurename, "%s/./.", securename);
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	printf("dotsecurename == %s\n", dotsecurename);
X	fflush(stdout);
X#endif /*DEBUG*/
X
X	/* let's see if our parent directory is around... */
X	if ((stat(parent, &sanity)) != 0)
X		error(parent, Doesnt_Exist, 0, 2);
X
X	/* find out if we can write here */
X	if (canIwrite(&sanity, myuserid, mygroupid) != 0) 
X		error(Cannot_Access, parent, 0, 2);
X
X	/* find out if we are going to stomp on something.. */
X	if ((stat(fullname, &sanity)) == 0) 
X		error(fullname, Already_Exist, 0, 2);
X
X	/* make secure parent directory (note the mode of 0) */
X	if (makedir(parent, securedir, 0) > 0) 
X		error(Secure_Failed, securedir, errno, 2);
X	
X	/* now make our directory underneath it */
X	if (makedir(parent, securename, 0777) > 0) {
X		rmdir(securedir);
X		error(Mkdir_Failed, securedir, errno, 3);
X	}
X
X	/* do that eerie little chown() that's the "root" of all our problems */
X	if (chown(dotsecurename, myuserid, mygroupid) != 0) {
X		rmdir(securename);
X		rmdir(securedir);
X		error(Chown_Failed, dotsecurename, errno, 3);
X	}
X	
X	/* do a quick sanity check, just to annoy someone trying
X	 * to trick mkdir into chowning something it shouldn't.. */
X	if ((stat(fullname, &sanity)) == 0) {
X		/* What happened?  This wasn't here a couple of calls ago... */
X		rmdir(securename);
X		rmdir(securedir);
X		error(fullname, Already_Exist, 0, 4);
X	}
X		
X	/* okay, put it where it belongs */
X	if ((link(securename, fullname)) < 0) {
X		rmdir(securename);
X		rmdir(securedir);
X		error(Cant_Link, fullname, errno, 4);
X	}
X	
X	/* remove all our rubbish, and tidy everything up.. */
X	if (parent != (char *)NULL) 
X		free(parent);
X	if (fullname != (char *)NULL)
X		free(fullname);
X	rval1 = unlink(securename);
X		/* Even if the unlink() fails, we really should at least
X		 * attempt to remove our scratch directory... */
X	rval2 = rmdir(securedir);
X	if (rval1 < 0)
X		error(Cant_Unlink, securename, errno, 5);
X	if (rval2 != 0)
X		error(Cant_Unlink, securedir, errno, 5);
X
X	return (0);
X}
X
int
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
int
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
int
print(s)
char *s;
X{
X	return (write(2, s, strlen(s)));
X}
X
error(s1, s2, err, ecode)
char *s1,
X     *s2;
int err,
X    ecode;
X{
X	print(Progname);
X	print(": ");
X	print(s1);
X	errno = err;
X	if (s2 != (char *)NULL)
X		print(s2);
X	if (err != 0) 
X		perror(" ");
X	else 
X		print("\n");
X	exit(ecode);
X}
X
int
set_sigs(func)
int *(*func)();
X{
X	register int i;
X
X	for (i=1; i<=NSIG ; i++)
X		if (signal(i, SIG_IGN) != SIG_IGN)
X			signal(i, func); /* point it at the catch() routine. */
X
X	return (0);
X}
X
int
canIwrite(stbuff, uid, gid)
register struct stat *stbuff;
register unsigned short uid,
X                        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)
X		if (stbuff->st_mode & 0200)
X			return (0);
X		else
X	/* okay, so I can't write here.. */
X			return (-1);
X
X	/* okay, so how about as a GROUP ? */
X	if (gid == stbuff->st_gid)
X		if (stbuff->st_mode & 0020)
X			return (0);
X		else
X	/* okay, so I can't write here.. */
X			return (-1);
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
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 11864 -ne `wc -c <'mkdir.c'`; then
    echo shar: \"'mkdir.c'\" unpacked with wrong size!
fi
# end of 'mkdir.c'
fi
echo shar: End of shell archive.
exit 0