home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume5 / safe-mkdir < prev    next >
Text File  |  1989-02-03  |  12KB  |  448 lines

  1. Path: xanth!nic.MR.NET!hal!ncoast!allbery
  2. From: doug@letni.UUCP (Doug Davis)
  3. Newsgroups: comp.sources.misc
  4. Subject: v05i085: mkdir() and security hole   *****FIX****
  5. Summary: how *TO* run a /bin/mkdir
  6. Keywords: mkdir hole fix
  7. Message-ID: <9465@merch.TANDY.COM>
  8. Date: 19 Dec 88 00:24:52 GMT
  9. Sender: allbery@ncoast.UUCP
  10. Reply-To: doug@letni.UUCP (Doug Davis)
  11. Organization: lawnet
  12. Lines: 433
  13. Approved: allbery@ncoast.UUCP
  14.  
  15. Posting-number: Volume 5, Issue 85
  16. Submitted-by: "Doug Davis" <doug@letni.UUCP>
  17. Archive-name: safe-mkdir
  18.  
  19. [I looked it over, looks OK from a quick scan -- but is it really secure?
  20. I *think* so, but....  ++bsa]
  21.  
  22. Attached is a version of /bin/mkdir that should eliminate the 
  23. problem with the race condition that someone can take advantage
  24. of to cause a major security hole.  Machines that have the 
  25. mkdir() function call, most anything based on 4.2 BSD or later,
  26. will not need this program. Everyone else that I know of
  27. should want this.
  28.  
  29. For security reasons I have elected not to describe the problem with /bin/mkdir
  30. fully, just suffice it to say that I have tested it on 11 differen't
  31. architectures and the "bug" existed on all of them.  If your /bin/mkdir
  32. program is setuid root, you too probably have this bug as well.
  33.  
  34. This mkdir first makes a directory to play in, which is owned by root
  35. and is mode 000.  It is made in the same directory in which the
  36. user is requesting his directory.  In this "secure" directory, to
  37. which the user allegedly has no access, the mknod(), chown(), and
  38. links for `.' and `..' are performed.  The new directory is then linked
  39. into place.  Finally, the "secure" directory is removed. Yes, there
  40. is a bit more overhead, but a much more secure program is worth it.
  41.  
  42. As usual, I will accept mail, suggestions, comments, etc will
  43. be appreciated. Flames will be ignored. If anyone can poke security holes
  44. in this code I would really like to hear about it.
  45.  
  46. BTW: I know the calls to rand() are not really needed. They just make
  47.      it more fun for someone trying to defeat the code.
  48.  
  49. Doug Davis
  50. --
  51. Lawnet
  52. 1030 Pleasent Valley Lane.
  53. Arlington Texas 76015
  54. 817-467-3740
  55. { sys1.tandy.com, motown!sys1, uiucuxc!sys1, killer!texbell } letni!doug
  56.  
  57.   "Talk about holes in UNIX, geeze thats nothing compaired with the security
  58.       problems in the ship control programs of StarFleet."
  59.  
  60. #! /bin/sh
  61. # This is a shell archive.  Remove anything before this line, then unpack
  62. # it by saving it into a file and typing "sh file".  To overwrite existing
  63. # files, type "sh file -c".  You can also feed this as standard input via
  64. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  65. # will see the following message at the end:
  66. #        "End of shell archive."
  67. # Contents:  Makefile mkdir.c
  68. # Wrapped by doug@letni on Thu Dec 15 01:28:50 1988
  69. # { sys1.tandy.com, motown!sys1, uiucuxc!sys1, killer!texbell } letni!doug
  70. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  71. if test -f 'Makefile' -a "${1}" != "-c" ; then 
  72.   echo shar: Will not clobber existing file \"'Makefile'\"
  73. else
  74. echo shar: Extracting \"'Makefile'\" \(510 characters\)
  75. sed "s/^X//" >'Makefile' <<'END_OF_FILE'
  76. X# What kind of strrchr do we have?
  77. X# 'strrchr' for most newer unix's,  'rindex' for earlier editions
  78. X# STTRCHR    = -DSTRRCHR=rindex
  79. STRRCHR    = -DSTRRCHR=strrchr
  80. X# do you have a rand() function call? 
  81. X# If you don't have one, comment out the line below
  82. RAND     = -DRAND
  83. X
  84. X# for debugging, 
  85. X# -DDEBUG
  86. X
  87. DEFINES    = $(STRRCHR) $(DEBUG) $(RAND)
  88. SHELL    =    /bin/sh
  89. CC        =    /bin/cc
  90. CFLAGS    =    -O $(DEFINES)
  91. LDFLAGS    =    -n -s
  92. X
  93. all: mkdir
  94. X
  95. mkdir.o: mkdir.c
  96. X    $(CC) $(CFLAGS) mkdir.c -c
  97. X
  98. mkdir: mkdir.o
  99. X    $(CC) $(LDFLAGS) mkdir.o -o mkdir
  100. END_OF_FILE
  101. if test 510 -ne `wc -c <'Makefile'`; then
  102.     echo shar: \"'Makefile'\" unpacked with wrong size!
  103. fi
  104. # end of 'Makefile'
  105. fi
  106. if test -f 'mkdir.c' -a "${1}" != "-c" ; then 
  107.   echo shar: Will not clobber existing file \"'mkdir.c'\"
  108. else
  109. echo shar: Extracting \"'mkdir.c'\" \(7645 characters\)
  110. sed "s/^X//" >'mkdir.c' <<'END_OF_FILE'
  111. X/*
  112. X * Secure mkdir program, solves that nasty race problem...
  113. X *
  114. X *    13 December 1988    Doug Davis         doug@lenti.lawnet.com
  115. X *                  and John Elliot IV     iv@trsvax.tandy.com
  116. X *     
  117. X *
  118. X *    Theory of operation:
  119. X *        This mkdir first makes a directory to play in, which is
  120. X *         owned by root and is mode 000.  It is made in the same
  121. X *        directory in which the user is requesting his directory.
  122. X *        In this "secure" directory, to which the user allegedly
  123. X *        has no access, the mknod(), chown(), and links for `.'
  124. X *        and `..' are performed.  The new directory is then linked
  125. X *        into place.  Finally, the "secure" directory is removed.
  126. X *
  127. X * This code copyright 1988 by Doug Davis (doug@letni.lawnet.com) 
  128. X *      You are free to modify, hack, fold, spindle, duplicate, pass-along
  129. X *      give-away, publish, transmit, or mutlate this code in any maner,
  130. X *      provided that you give credit where credit is due and don't pretend
  131. X *      that you wrote it.
  132. X * 
  133. X *  If you do my lawyers (and I have a lot of lawyers) will teach you a lesson
  134. X *  or two in copyright law that you will never ever forget.
  135. X */
  136. X
  137. X#define MAXPATHLEN    128        /* maximum reasonanble path length */
  138. X
  139. X#include <sys/types.h>
  140. X#include <signal.h>
  141. X#include <sys/stat.h>
  142. X#ifdef DEBUG
  143. X#  include <stdio.h>
  144. X#else /*DEBUG*/
  145. X#  define NULL ((char *) 0)
  146. X#endif /*DEBUG*/
  147. X
  148. X#define MKNODE    1
  149. X#define LINK    2
  150. X
  151. char *Malloc_Failed    = "malloc() failed.";
  152. char *Doesnt_Exist    = " does not exist.";
  153. char *Cannot_Access    = "cannot access ";
  154. char *Already_Exist    = " already exists.";
  155. char *Secure_Failed    = "makedir secure parent failed ";
  156. char *Couldnt_Link    = "Couldn't link to ";
  157. char *Mkdir_Failed    = "makedir failed ";
  158. char *Chown_Failed    = "chown() failed ";
  159. X
  160. extern char *STRRCHR();
  161. extern char *malloc();
  162. X
  163. extern int errno;
  164. extern int getpid();
  165. X
  166. extern unsigned short getgid();
  167. extern unsigned short getuid();
  168. X
  169. X#ifdef RAND
  170. extern int rand();
  171. X#else /*RAND*/
  172. extern int getppid();
  173. X#endif /*RAND*/
  174. X
  175. extern long time();
  176. X
  177. char *Progname;
  178. X
  179. main(argc, argv)
  180. int argc;
  181. char *argv[];
  182. X{
  183. X    Progname = argv[0];
  184. X    errno = 0;
  185. X
  186. X    if (argc < 2) {
  187. X        print("Usage:  ");
  188. X        print(Progname);
  189. X        print(" directory_name [ ... directory_name ]\n");
  190. X        exit(0);
  191. X    }
  192. X
  193. X    /* Catch those nasty signals that could cause us
  194. X     * to mess up the filesystem */
  195. X    swat_sigs();
  196. X
  197. X    while (--argc)
  198. X        md(*++argv); /* make each directory */
  199. X
  200. X    exit(errno);
  201. X}
  202. X
  203. X
  204. md(s)
  205. char *s;
  206. X{
  207. X    char    *basename, *parent, *fullname;
  208. X    char    securename[MAXPATHLEN], securedir[MAXPATHLEN];
  209. X    long    snum;
  210. X    unsigned short myuserid, mygroupid;
  211. X    struct    stat sanity;
  212. X
  213. X    /* find out who I really am */
  214. X    myuserid = getuid();
  215. X    mygroupid = getgid();
  216. X
  217. X    /* set up the pseudo-RANDom number generation system */
  218. X#ifndef RAND
  219. X    srand(getpid());
  220. X#endif /*RAND*/
  221. X
  222. X    /* see if we are explicit or indirect */
  223. X    basename = STRRCHR(s, '/');
  224. X    if (basename == (char *) NULL) {
  225. X        fullname = malloc(strlen(s)+1);
  226. X        if (fullname == (char *) NULL) 
  227. X            error(Malloc_Failed, NULL, errno);
  228. X        parent = malloc(2);
  229. X        if (parent == (char *) NULL)
  230. X            error(Malloc_Failed, NULL, errno);
  231. X        parent[0] = '.';
  232. X        parent[1] = '\0';
  233. X        strcpy(fullname, s);
  234. X        basename = s;
  235. X    } else {
  236. X        fullname = malloc(strlen(s)+1);
  237. X        if (fullname == (char *) NULL) 
  238. X            error(Malloc_Failed, NULL, errno);
  239. X        strcpy(fullname, s);
  240. X        *basename = '\0';
  241. X        basename++;
  242. X        parent = malloc(strlen(s) + 3);
  243. X        if (parent == (char *) NULL)
  244. X            error(Malloc_Failed, NULL, errno);
  245. X        strcpy(parent, s);
  246. X        strcat(parent, "/.");
  247. X    }
  248. X
  249. X    /* Generate the secure names ... */
  250. X    do {
  251. X        /* round and round we go where we stop depends on
  252. X         * the non-existance of securedir */
  253. X        snum = time((int *) 0);    
  254. X#ifdef RAND
  255. X        sprintf(securedir, "%s/%ld", parent, snum - (long)rand());
  256. X        sprintf(securename, "%s/%ld", securedir, snum + (long)rand());
  257. X#else /*RAND*/
  258. X        sprintf(securedir, "%s/%ld", parent, snum - (long)getppid());
  259. X        sprintf(securename, "%s/%ld", securedir, snum + (long)getppid());
  260. X        snum += (long)getpid();
  261. X#endif /*RAND*/
  262. X    } while (stat(securedir, &sanity) == 0);
  263. X
  264. X#ifdef DEBUG
  265. X    /* spill the beans .. */
  266. X    printf("parent     == %s\n", parent);
  267. X    printf("basename   == %s\n", basename);
  268. X    printf("fullname   == %s\n", fullname);
  269. X    printf("securedir  == %s\n", securedir);
  270. X    printf("securename == %s\n", securename);
  271. X    fflush(stdout);
  272. X#endif /*DEBUG*/
  273. X
  274. X    /* lets see if our parent directory is around... */
  275. X    if ((stat(parent, &sanity)) != 0)
  276. X        error(parent, Doesnt_Exist, 0);
  277. X
  278. X    /* find out if we can write here */
  279. X    if (canIwrite(&sanity, myuserid, mygroupid) != 0) 
  280. X        error(Cannot_Access, parent, 0);
  281. X
  282. X    /* find out if we are going to stomp on something.. */
  283. X    if ((stat(fullname, &sanity)) == 0) 
  284. X        error(fullname, Already_Exist, 0);
  285. X
  286. X    /* make secure parent directory (note the mode of 0) */
  287. X    if (makedir(parent, securedir, 0) > 0) 
  288. X        error(Secure_Failed, securedir, errno);
  289. X    
  290. X    /* now make our directory underneath it */
  291. X    if (makedir(parent, securename, 0777) > 0) 
  292. X        error(Mkdir_Failed, securedir, errno);
  293. X
  294. X    /* do that eerie little chown() thats the "root" of all our problems */
  295. X    if (chown(securename, myuserid, mygroupid) != 0) 
  296. X        error(Chown_Failed, securename, errno);
  297. X    
  298. X    /* do a quick sanity check, just to annoy someone trying, unsccessfully
  299. X     * I might add, to trick mkdir into chowning something it shouldn't.. */
  300. X    if ((stat(fullname, &sanity)) == 0) {
  301. X        /* what happend? this wasn't here a couple of functions ago.. */
  302. X        unlink(securename);
  303. X        rmdir(securedir);
  304. X        error(fullname, Already_Exist, 0);
  305. X    }
  306. X        
  307. X    /* okay, put it where it belongs */
  308. X    if ((link(securename, fullname)) < 0) 
  309. X        error(Couldnt_Link, fullname, errno);
  310. X    
  311. X    /* remove all our rubbish, and tidy everything up.. */
  312. X    unlink(securename);
  313. X    rmdir(securedir);
  314. X    if (parent != (char *) NULL) 
  315. X        free(parent);
  316. X    if (fullname != (char *) NULL)
  317. X        free(fullname);
  318. X    return(0);
  319. X}
  320. X
  321. makedir(parent, dir, mode)
  322. char *parent, *dir;
  323. int mode;
  324. X{
  325. X    char dotdot[MAXPATHLEN];
  326. X
  327. X#ifdef DEBUG
  328. X    printf("mkdir(%s, %s)\n", parent, dir);
  329. X    fflush(stdout);
  330. X#endif /*DEBUG*/
  331. X
  332. X    /* put the node together */
  333. X    if ((mknod(dir, S_IFDIR | mode, 0)) < 0) 
  334. X        return (MKNODE);
  335. X
  336. X    /* make dot */
  337. X    strcpy(dotdot, dir);
  338. X    strcat(dotdot, "/.");
  339. X    if ((link(dir, dotdot)) < 0) 
  340. X        return (LINK);
  341. X
  342. X    /* make dotdot */
  343. X    strcat(dotdot, ".");
  344. X    if ((link(parent, dotdot)) < 0) 
  345. X        return (LINK);
  346. X
  347. X    return (0);
  348. X}
  349. X
  350. rmdir(dir)
  351. char *dir;
  352. X{
  353. X    char dots[MAXPATHLEN];
  354. X
  355. X#ifdef DEBUG
  356. X    printf("rmdir(%s)\n", dir);
  357. X    fflush(stdout);
  358. X#endif /*DEBUG*/
  359. X
  360. X    strcpy(dots, dir);
  361. X    strcat(dots, "/.");
  362. X
  363. X    /* unlink(".") */
  364. X    if (unlink(dots) < 0)
  365. X        return (LINK);
  366. X
  367. X    /* unlink("..") */
  368. X    strcat(dots, ".");
  369. X    if (unlink(dots) < 0)
  370. X        return (LINK);
  371. X
  372. X    /* unlink the directory itself */
  373. X    if (unlink(dir) < 0)
  374. X        return (LINK);
  375. X
  376. X    return (0);
  377. X}
  378. X
  379. print(s)
  380. char *s;
  381. X{
  382. X    write(2, s, strlen(s));
  383. X}
  384. X
  385. error(s1, s2, err)
  386. char *s1, *s2;
  387. int err;
  388. X{
  389. X    write(2, Progname, strlen(Progname));
  390. X    write(2, ": ", 2);
  391. X    write(2, s1, strlen(s1));
  392. X    errno = err;
  393. X    if (s2 != NULL)
  394. X        write(2, s2, strlen(s2));
  395. X    if (err != 0) 
  396. X        perror(" ");
  397. X    else 
  398. X        write(2, "\n", 1);
  399. X    exit(errno);
  400. X}
  401. swat_sigs()
  402. X{
  403. X    register int i;
  404. X
  405. X    for (i=SIGHUP; i<=NSIG ; i++)
  406. X        signal(i, SIG_IGN); /* bye-bye */
  407. X}
  408. canIwrite(stbuff, uid, gid)
  409. register struct stat *stbuff;
  410. register unsigned short uid, gid;
  411. X{
  412. X    /* we let root get away with anything... */
  413. X    if (uid == 0)
  414. X        return(0);
  415. X
  416. X    /* can I write in it as an OWNER ? */
  417. X    if (uid == stbuff->st_uid && stbuff->st_mode & 0200) 
  418. X        return(0);
  419. X
  420. X    /* okay, so how about as a GROUP ? */
  421. X    if (gid == stbuff->st_gid && stbuff->st_mode & 0020)
  422. X        return(0);
  423. X
  424. X    /* alright, how about an OTHER ? */
  425. X    if (stbuff->st_mode & 0002)
  426. X        return(0);
  427. X
  428. X    /* okay, so I can't write here.. */
  429. X    return(-1);
  430. X}
  431. X#ifdef DEBUG
  432. unlink(s)
  433. char *s;
  434. X{
  435. X    printf("Unlink(%s)\n", s);
  436. X    fflush(stdout);
  437. X}
  438. X#endif /*DEBUG*/
  439. END_OF_FILE
  440. if test 7645 -ne `wc -c <'mkdir.c'`; then
  441.     echo shar: \"'mkdir.c'\" unpacked with wrong size!
  442. fi
  443. chmod +x 'mkdir.c'
  444. # end of 'mkdir.c'
  445. fi
  446. echo shar: End of shell archive.
  447. exit 0
  448.