home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / compsrcs / unix / volume22 / indir.pch < prev    next >
Encoding:
Internet Message Format  |  1990-05-04  |  13.9 KB

  1. Path: wuarchive!zaphod.mps.ohio-state.edu!usc!apple!bbn!papaya.bbn.com!rsalz
  2. From: rsalz@bbn.com (Rich Salz)
  3. Newsgroups: comp.sources.unix
  4. Subject: v22i010:  Patch to indir -- safely execute (set[ug]id) scripts
  5. Message-ID: <2481@papaya.bbn.com>
  6. Date: 2 May 90 19:21:34 GMT
  7. Organization: BBN Systems and Technologies, Cambridge MA
  8. Lines: 390
  9. Approved: rsalz@uunet.UU.NET
  10. X-Checksum-Snefru: b3f85d0f 8d8c5d5a cc236020 fadee82a
  11.  
  12. Submitted-by: Maarten Litmaath <maart@cs.vu.nl>
  13. Posting-number: Volume 22, Issue 10
  14. Archive-name: indir.pch
  15. Patch-to: volume21/indir
  16.  
  17. There was a small (== not fundamental) problem with indir 2.0.
  18. Thanks to J. Greely <jgreely@cis.ohio-state.edu> for pointing it out:
  19.  
  20. >(from setuid.txt)
  21. >>Answer: if and only if the file we're reading from is SETUID (setgid) to the
  22. >>EFFECTIVE uid (gid) of the process, we know we're executing the original
  23. >>script (to be 100% correct: the original link might have been replaced with a
  24. >>link to ANOTHER setuid script of the same owner -> merely a waste of time).
  25. >
  26. >This is not necessarily a waste of time.  If there are several scripts
  27. >set-id to the same thing, some of which are protected from public
  28. >access by directory permissions, indir can't tell which one I'm trying
  29. >to execute unless it checks to see if the REAL uid (gid) has
  30. >permission to execute the script (at which point it doesn't know that
  31. >it's the *right* script, but it's one they could have executed
  32. >directly anyway, so no big deal).
  33.  
  34. The main point of indir 2.0 was to make it impossible to execute *arbitrary*
  35. scripts instead of the intended setuid (setgid) script, i.e. only setuid
  36. (setgid) scripts of the correct owner (group) could be executed; version 2.3
  37. also checks if the script about to get executed really is accessible to the
  38. real uid (gid), thereby closing the last hole.
  39.  
  40. Here's an example:
  41.  
  42.     % pwd
  43.     /some/dir
  44.     % ls -ldg baz foo foo/bar
  45.     -rwsr-xr-x  1 root     wheel       16384 Apr 25  1989 baz
  46.     drwxr-x--- 12 root     wheel         512 Mar 12 23:29 foo
  47.     -rwsr-x---  1 root     wheel       24576 Mar  5 11:13 foo/bar
  48.     % id
  49.     uid=1234(john) gid=56(guest) groups=56(guest)
  50.     % cd /tmp
  51.     % ln -s /some/dir/baz baz
  52.     % /bin/nice -20 ./baz &
  53.     % rm baz
  54.     % ln -s /some/dir/foo/bar baz
  55.     %
  56.  
  57. If the race condition `succeeds', we end up executing `/some/dir/foo/bar'
  58. instead of `/some/dir/baz'.
  59. Measures:
  60.  
  61. 1)    /some/dir/foo/bar isn't executable for `john'; indir 2.3 checks if
  62.     the file it's reading from *is* executable for the *real* uid (gid),
  63.     using fstat(2).
  64. 2)    Even if it was executable, /some/dir/foo wasn't accessible for `john';
  65.     indir 2.3 checks if the real uid (gid) can stat(2) its second argument
  66.     (here `./baz').
  67.     To make sure this file really is the file we're reading from, inode
  68.     and device numbers are compared.
  69. 3)    If your UNIX version doesn't have symbolic links, you can't link to a
  70.     file in an inaccessible directory, therefore only point 1 remains.
  71.  
  72. To apply the patches below: in the directory containing the sources of indir
  73. 2.0 type `patch < diffs', assuming the cdiffs have been saved in the file
  74. `diffs'.
  75.  
  76.                 Maarten Litmaath @ VU Amsterdam:
  77.                 maart@cs.vu.nl, uunet!mcsun!botter!maart
  78.  
  79. : This is a shar archive.  Extract with sh, not csh.
  80. : This archive ends with exit, so do not worry about trailing junk.
  81. : --------------------------- cut here --------------------------
  82. PATH=/bin:/usr/bin:/usr/ucb
  83. echo Extracting 'diffs'
  84. sed 's/^X//' > 'diffs' << '+ END-OF-FILE ''diffs'
  85. X*** Makefile.B    Tue Mar 27 05:14:58 1990
  86. X--- Makefile    Tue Mar 27 04:59:43 1990
  87. X***************
  88. X*** 12,19 ****
  89. X  CC        = cc
  90. X  # if your C library doesn't have strrchr()
  91. X  # STRRCHR    = -Dstrrchr=rindex
  92. X  
  93. X! CFLAGS        = -O $(STRRCHR)
  94. X  
  95. X  indir:        exec_ok $(OBJS)
  96. X          $(CC) -O -o indir $(OBJS)
  97. X--- 12,21 ----
  98. X  CC        = cc
  99. X  # if your C library doesn't have strrchr()
  100. X  # STRRCHR    = -Dstrrchr=rindex
  101. X+ # if your UNIX version has the union wait (see wait(2))
  102. X+ UNION_WAIT    = -DUNION_WAIT
  103. X  
  104. X! CFLAGS        = -O $(STRRCHR) $(UNION_WAIT)
  105. X  
  106. X  indir:        exec_ok $(OBJS)
  107. X          $(CC) -O -o indir $(OBJS)
  108. X*** error.h.B    Tue Mar 27 02:10:50 1990
  109. X--- error.h    Tue Mar 27 02:11:59 1990
  110. X***************
  111. X*** 40,44 ****
  112. X--- 40,45 ----
  113. X  ERROR(E_mode,  12, "%s: `%s' is set[ug]id, yet has checking disabled!\n")
  114. X  ERROR(E_alarm, 13, "%s: `%s' is a fake!\n")
  115. X  ERROR(E_exec,  14, "%s: cannot execute `%s' in `%s': %s\n")
  116. X+ ERROR(E_fork,  15, "%s: cannot fork in `%s': %s\n")
  117. X  
  118. X  #endif    /* !ERROR_H */
  119. X*** indir.1.B    Tue Mar 27 03:07:32 1990
  120. X--- indir.1    Sat Mar 31 00:42:24 1990
  121. X***************
  122. X*** 102,112 ****
  123. X  3) before the final \fIexecve\fR(2)
  124. X  .I indir
  125. X  checks if the owner and mode of the script are still what they are supposed
  126. X! to be (using \fIfstat\fR(2)); if there is a discrepancy,
  127. X  .I indir
  128. X  will abort with an error message.
  129. X  .RE
  130. X  .PP
  131. X  .I Indir
  132. X  will only exec a pathname beginning with a `\fB/\fR', unless the `\-\fIn\fR'
  133. X  option was specified. In the latter case the user's PATH will be searched
  134. X--- 102,121 ----
  135. X  3) before the final \fIexecve\fR(2)
  136. X  .I indir
  137. X  checks if the owner and mode of the script are still what they are supposed
  138. X! to be (using \fIfstat\fR(2)), and if the user really can access and execute
  139. X! it (using \fIstat\fR(2)); \fIinode\fR and \fIdevice\fR numbers are compared
  140. X! to make sure all checks refer to the same file. If any discrepancy is found,
  141. X  .I indir
  142. X  will abort with an error message.
  143. X  .RE
  144. X  .PP
  145. X+ Feature: \fIindir\fR always checks if the REAL uid (gid) has access to the
  146. X+ setuid (setgid) script, even if the effective id already differed
  147. X+ from the real id BEFORE the script was executed.  (There isn't even
  148. X+ a way to find out the original effective id.)
  149. X+ If you want the original effective id to be used, you should set
  150. X+ the real id accordingly before executing the script.
  151. X+ .PP
  152. X  .I Indir
  153. X  will only exec a pathname beginning with a `\fB/\fR', unless the `\-\fIn\fR'
  154. X  option was specified. In the latter case the user's PATH will be searched
  155. X***************
  156. X*** 124,127 ****
  157. X  .SH BUGS
  158. X  The maintenance of setuid (setgid) scripts is a bit annoying: if a script is
  159. X  moved, one must not forget to change the path mentioned in the script.
  160. X! Possibly the editing causes a setuid bit to get turned off.
  161. X--- 133,136 ----
  162. X  .SH BUGS
  163. X  The maintenance of setuid (setgid) scripts is a bit annoying: if a script is
  164. X  moved, one must not forget to change the path mentioned in the script.
  165. X! Possibly the editing causes a setuid (setgid) bit to get turned off.
  166. X*** indir.c.B    Tue Mar 27 03:40:36 1990
  167. X--- indir.c    Sat Mar 31 00:59:41 1990
  168. X***************
  169. X*** 1,4 ****
  170. X! static    char    sccsid[] = "@(#)indir.c 2.0 89/11/01 Maarten Litmaath";
  171. X  
  172. X  /*
  173. X   * indir.c
  174. X--- 1,4 ----
  175. X! static    char    sccsid[] = "@(#)indir.c 2.3 90/03/31 Maarten Litmaath";
  176. X  
  177. X  /*
  178. X   * indir.c
  179. X***************
  180. X*** 98,104 ****
  181. X       * !Uid_check !setuid -> OK: ordinary script
  182. X       * !Uid_check  setuid -> security hole: checking should be enabled
  183. X       *  Uid_check !setuid -> fake
  184. X!      *  Uid_check  setuid -> check if st_uid == euid
  185. X       */
  186. X  
  187. X      if (!Uid_check) {
  188. X--- 98,105 ----
  189. X       * !Uid_check !setuid -> OK: ordinary script
  190. X       * !Uid_check  setuid -> security hole: checking should be enabled
  191. X       *  Uid_check !setuid -> fake
  192. X!      *  Uid_check  setuid -> check if st_uid == euid and if user has
  193. X!      *                       access permissions
  194. X       */
  195. X  
  196. X      if (!Uid_check) {
  197. X***************
  198. X*** 112,121 ****
  199. X      } else {
  200. X          /*
  201. X           * Check if the file we're reading from is setuid and owned
  202. X!          * by geteuid().  If this test fails, the file is a fake,
  203. X!          * else it MUST be ok!
  204. X           */
  205. X!         if (!(st.st_mode & S_ISUID) || st.st_uid != geteuid())
  206. X              error(E_alarm, Prog, File);
  207. X      }
  208. X  
  209. X--- 113,125 ----
  210. X      } else {
  211. X          /*
  212. X           * Check if the file we're reading from is setuid and owned
  213. X!          * by geteuid().  If this test fails, the file is a fake.
  214. X!          * Then check if the real uid can execute the file at all:
  215. X!          * he could have used a link()/unlink() scheme to get us to
  216. X!          * execute an inaccessible script.
  217. X           */
  218. X!         if (!(st.st_mode & S_ISUID) || st.st_uid != geteuid()
  219. X!             || chkperm(&st, File) < 0)
  220. X              error(E_alarm, Prog, File);
  221. X      }
  222. X  
  223. X***************
  224. X*** 125,137 ****
  225. X          if (st.st_mode & S_ISGID)
  226. X              error(E_mode, Prog, File);
  227. X      } else {
  228. X!         if (!(st.st_mode & S_ISGID) || st.st_gid != getegid())
  229. X              error(E_alarm, Prog, File);
  230. X      }
  231. X  
  232. X      /*
  233. X       * If we're executing a set[ug]id file, replace the complete
  234. X!      * environment by a save default, else permit the PATH to be
  235. X       * searched too.
  236. X       */
  237. X      if (st.st_mode & (S_ISUID | S_ISGID))
  238. X--- 129,142 ----
  239. X          if (st.st_mode & S_ISGID)
  240. X              error(E_mode, Prog, File);
  241. X      } else {
  242. X!         if (!(st.st_mode & S_ISGID) || st.st_gid != getegid()
  243. X!             || chkperm(&st, File) < 0)
  244. X              error(E_alarm, Prog, File);
  245. X      }
  246. X  
  247. X      /*
  248. X       * If we're executing a set[ug]id file, replace the complete
  249. X!      * environment by a safe default, else permit the PATH to be
  250. X       * searched too.
  251. X       */
  252. X      if (st.st_mode & (S_ISUID | S_ISGID))
  253. X***************
  254. X*** 252,257 ****
  255. X--- 257,335 ----
  256. X      while ((c = *p++) != ' ' && c != '\t' && c != '\n')
  257. X          ;
  258. X      return --p;
  259. X+ }
  260. X+ 
  261. X+ 
  262. X+ #define        X_USR        S_IXUSR        /* 0100 */
  263. X+ #define        X_GRP        S_IXGRP        /* 0010 */
  264. X+ #define        X_OTH        S_IXOTH        /* 0001 */
  265. X+ 
  266. X+ 
  267. X+ static    int    chkperm(st, f)
  268. X+ struct    stat    *st;
  269. X+ char    *f;
  270. X+ {
  271. X+     struct    stat    stbuf;
  272. X+     int    xmask, pid;
  273. X+     uid_t    uid;
  274. X+     gid_t    gid;
  275. X+     static    int    status = -1, checked = 0;
  276. X+ #ifdef    UNION_WAIT
  277. X+     union    wait    w;
  278. X+ #define        ok(w)        (w.w_status == 0)
  279. X+ #else
  280. X+     int    w;
  281. X+ #define        ok(w)        (w == 0)
  282. X+ #endif    /* UNION_WAIT */
  283. X+ 
  284. X+     if (checked)
  285. X+         return status;
  286. X+     checked = 1;
  287. X+ 
  288. X+     /*
  289. X+      * If `Uid_check' (`Gid_check') has been set, we're executing a
  290. X+      * setuid (setgid) script, so use the real uid (gid) in the access
  291. X+      * check; else use the effective uid (gid), just like the kernel does.
  292. X+      * Feature: we always check if the REAL uid (gid) has access to the
  293. X+      * setuid (setgid) script, even if the effective id already differed
  294. X+      * from the real id BEFORE the script was executed.  (There isn't even
  295. X+      * a way to find out the original effective id.)
  296. X+      * If you want the original effective id to be used, you should set
  297. X+      * the real id accordingly before executing the script.
  298. X+      */
  299. X+     uid = Uid_check ? Uid : geteuid();
  300. X+     gid = Gid_check ? getgid() : getegid();
  301. X+ 
  302. X+     xmask = (Uid == 0) ? (X_USR | X_GRP | X_OTH) :
  303. X+         st->st_uid == uid ? X_USR :
  304. X+         st->st_gid == gid ? X_GRP :
  305. X+         X_OTH;
  306. X+     /*
  307. X+      * Can the invoker really execute the file we're reading from?
  308. X+      */
  309. X+     if (!(st->st_mode & xmask))
  310. X+         return -1;
  311. X+ 
  312. X+     switch (pid = fork()) {
  313. X+     case -1:
  314. X+         error(E_fork, Prog, File, geterr());
  315. X+     case 0:
  316. X+         if (Uid_check)
  317. X+             (void) setuid(uid);        /* reset uid */
  318. X+         if (Gid_check)
  319. X+             (void) setgid(gid);        /* reset gid */
  320. X+         /*
  321. X+          * Now check if the `real' uid (gid) can access the file
  322. X+          * we're reading from, i.e. if the leading directories are
  323. X+          * searchable.  Compare `st_ino' and `st_dev' to make sure
  324. X+          * we're talking about the same file.
  325. X+          */
  326. X+         exit(stat(f, &stbuf) < 0 || stbuf.st_ino != st->st_ino
  327. X+             || stbuf.st_dev != st->st_dev);
  328. X+     }
  329. X+     while (wait(&w) != pid)
  330. X+         ;
  331. X+     return ok(w) ? status = 0 : -1;
  332. X  }
  333. X  
  334. X  
  335. X*** indir.h.B    Tue Mar 27 04:47:03 1990
  336. X--- indir.h    Tue Mar 27 04:47:34 1990
  337. X***************
  338. X*** 1,6 ****
  339. X--- 1,9 ----
  340. X  #include    <sys/param.h>
  341. X  #include    <sys/stat.h>
  342. X  #include    <stdio.h>
  343. X+ #ifdef    UNION_WAIT
  344. X+ #include    <sys/wait.h>
  345. X+ #endif    /* UNION_WAIT */
  346. X  
  347. X  #define        COMMENT        '#'
  348. X  #define        MAGIC        '?'
  349. X*** setuid.txt.B    Tue Mar 27 02:45:31 1990
  350. X--- setuid.txt    Sat Mar 31 00:26:58 1990
  351. X***************
  352. X*** 103,114 ****
  353. X  the link to the script might have been quickly replaced with a link to another
  354. X  script, i.e. how can we trust this `#?' line?
  355. X  Answer: if and only if the file we're reading from is SETUID (setgid) to the
  356. X! EFFECTIVE uid (gid) of the process, we know we're executing the original
  357. X! script (to be 100% correct: the original link might have been replaced with a
  358. X! link to ANOTHER setuid script of the same owner -> merely a waste of time).
  359. X! To reliably check the condition stated above, we use fstat(2) on the file
  360. X! descriptor we're reading from. Can you figure out why stat(2) would be
  361. X! insecure?
  362. X  To deal with IFS, PATH and other environment problems, indir(1) resets the
  363. X  environment to a simple default:
  364. X  
  365. X--- 103,125 ----
  366. X  the link to the script might have been quickly replaced with a link to another
  367. X  script, i.e. how can we trust this `#?' line?
  368. X  Answer: if and only if the file we're reading from is SETUID (setgid) to the
  369. X! EFFECTIVE uid (gid) of the process, AND it's accessible and executable for
  370. X! the REAL uid (gid), we know we're executing the original script (to be 100%
  371. X! correct: the original link might have been replaced with a link to ANOTHER
  372. X! accessible and executable setuid (setgid) script of the same owner (group)
  373. X! -> merely a waste of time).
  374. X! To check the condition stated above reliably, we use fstat(2) on the file
  375. X! descriptor we're reading from, and stat(2) on the associated file name.
  376. X! We compare inode and device numbers to make sure we're talking about the
  377. X! same file.  Can you figure out why using stat(2) alone would be insecure?
  378. X! 
  379. X! Feature: we always check if the REAL uid (gid) has access to the setuid
  380. X! (setgid) script, even if the effective id already differed from the real id
  381. X! BEFORE the script was executed.  (There isn't even a way to find out the
  382. X! original effective id.)
  383. X! If you want the original effective id to be used, you should set the real id
  384. X! accordingly before executing the script.
  385. X! 
  386. X  To deal with IFS, PATH and other environment problems, indir(1) resets the
  387. X  environment to a simple default:
  388. X  
  389. + END-OF-FILE diffs
  390. chmod 'u=rw,g=r,o=r' 'diffs'
  391. set `wc -c 'diffs'`
  392. count=$1
  393. case $count in
  394. 10201)    :;;
  395. *)    echo 'Bad character count in ''diffs' >&2
  396.         echo 'Count should be 10201' >&2
  397. esac
  398. exit 0
  399. -- 
  400. Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
  401. Use a domain-based address or give alternate paths, or you may lose out.
  402.