home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume22 / indir.pch < prev    next >
Text File  |  1990-06-07  |  14KB  |  393 lines

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