doug@letni.UUCP (Doug Davis) (12/19/88)
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