home *** CD-ROM | disk | FTP | other *** search
- 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
-