home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / compsrcs / unix / volume21 / indir < prev    next >
Encoding:
Internet Message Format  |  1990-03-29  |  29.6 KB

  1. Path: wuarchive!mit-eddie!bbn!papaya.bbn.com!rsalz
  2. From: rsalz@uunet.uu.net (Rich Salz)
  3. Newsgroups: comp.sources.unix
  4. Subject: v21i031:  Safe way to run setuid shell scripts
  5. Message-ID: <2358@litchi.bbn.com>
  6. Date: 23 Mar 90 20:14:15 GMT
  7. Lines: 1149
  8. Approved: rsalz@uunet.UU.NET
  9. X-Checksum-Snefru: 4f572b9c 2ce9cfdd 5b62cea8 69ef7415
  10.  
  11. Submitted-by: Maarten Litmaath <maart@cs.vu.nl>
  12. Posting-number: Volume 21, Issue 31
  13. Archive-name: indir
  14.  
  15. Suppose you want everyone to be able to remove some lockfile, but you don't
  16. want its directory to be world-writable.  Isn't it ridiculous you'd have to
  17. write a setuid C program to do the equivalent of the following shell script?
  18.  
  19.     #!/bin/sh
  20.     /bin/rm /some/directory/lockfile
  21.  
  22. The problem: making this shell script setuid creates a security hole (see
  23. the file `setuid.txt').  The solution: indir(1).  Using this program the
  24. script would be setuid and look like this:
  25.  
  26.     #!/bin/indir -u
  27.     #?/bin/sh /safe/path/to/this/script
  28.     /bin/rm /some/directory/lockfile
  29.  
  30. To make indir, check if the Makefile is suited for your environment.
  31. The `exec_test' script will find out if your operating system has `#!'
  32. magic numbers enabled.  To do some tests, issue the command `make test'.
  33.  
  34. : This is a shar archive.  Extract with sh, not csh.
  35. : This archive ends with exit, so do not worry about trailing junk.
  36. : --------------------------- cut here --------------------------
  37. PATH=/bin:/usr/bin:/usr/ucb
  38. echo Extracting 'README'
  39. sed 's/^X//' > 'README' << '+ END-OF-FILE ''README'
  40. X`Intro' describes why one would use indir(1), `setuid.txt' is a general
  41. Xtext about setuid shell scripts.
  42. XTo make indir, check if the Makefile is suited for your environment.
  43. XThe `exec_test' script will find out if your operating system has `#!'
  44. Xmagic numbers enabled.  To do some tests, issue the command `make test'.
  45. + END-OF-FILE README
  46. chmod 'u=rw,g=r,o=r' 'README'
  47. set `wc -c 'README'`
  48. count=$1
  49. case $count in
  50. 318)    :;;
  51. *)    echo 'Bad character count in ''README' >&2
  52.         echo 'Count should be 318' >&2
  53. esac
  54. echo Extracting 'Intro'
  55. sed 's/^X//' > 'Intro' << '+ END-OF-FILE ''Intro'
  56. X            Why use indir(1)?
  57. X            -----------------
  58. X
  59. XSuppose you want everyone to be able to remove some lockfile, but you don't
  60. Xwant its directory to be world-writable.  Isn't it ridiculous you'd have to
  61. Xwrite a setuid C program to do the equivalent of the following shell script?
  62. X
  63. X    #!/bin/sh
  64. X    /bin/rm /some/directory/lockfile
  65. X
  66. XThe problem: making this shell script setuid creates a security hole (see
  67. Xthe file `setuid.txt').  The solution: indir(1).  Using this program the
  68. Xscript would be setuid and look like this:
  69. X
  70. X    #!/bin/indir -u
  71. X    #?/bin/sh /safe/path/to/this/script
  72. X    /bin/rm /some/directory/lockfile
  73. X
  74. XAs the only command in the script is `rm', you might think the script could
  75. Xlook like this too:
  76. X
  77. X    #!/bin/indir -u
  78. X    #?/bin/rm /some/directory/lockfile
  79. X
  80. XHowever, this is WRONG, because if the script is invoked with arguments,
  81. Xthose will also be arguments to `rm':
  82. X
  83. X    remove_lockfile foo bar
  84. X
  85. Xbecomes
  86. X
  87. X    /bin/rm /some/directory/lockfile foo bar
  88. X
  89. Xclearly not what was intended.
  90. XIndir(1) can also be used for non-setuid scripts, to overcome the constraints
  91. Xon a `#!' line (currently max. 32 characters, max. 1 (option) argument and the
  92. X`interpreter' must be a real binary):
  93. X
  94. X    #!/bin/indir -n
  95. X    #?sed -n -f %
  96. X    /uu/p
  97. X
  98. XIf invoked with the `-n' option, indir(1) will substitute the name of the
  99. Xscript for every `%' argument on the `#?' line.  Furthermore the user's
  100. XPATH will be searched (if necessary) to find the program to be executed.
  101. XEven in setuid scripts every `#?' argument is subject to the `~' convention:
  102. Xa leading string `~user' will be expanded to the home directory of `user'.
  103. X
  104. XSome points of interest and advantages over other schemes:
  105. X- one is not bound to the 32-characters-1-option limit
  106. X- the `interpreter' needn't be a real binary (another `#!' constraint)
  107. X- `%' substitution for normal scripts, `~' convention for all scripts
  108. X- only 1, public program needed, instead of each user having his own setuid
  109. X  `server'
  110. X- no database of valid scripts (`services') needed
  111. X- one can make a private link to the script, with a prefered name, and things
  112. X  still work right:
  113. X
  114. X    $ ln -s /etc/setuid_script my_name
  115. X    $ my_name
  116. + END-OF-FILE Intro
  117. chmod 'u=rw,g=r,o=r' 'Intro'
  118. set `wc -c 'Intro'`
  119. count=$1
  120. case $count in
  121. 2118)    :;;
  122. *)    echo 'Bad character count in ''Intro' >&2
  123.         echo 'Count should be 2118' >&2
  124. esac
  125. echo Extracting 'setuid.txt'
  126. sed 's/^X//' > 'setuid.txt' << '+ END-OF-FILE ''setuid.txt'
  127. X            Setuid Shell Scripts
  128. X            --------------------
  129. X            how to get them safe
  130. X
  131. X              Maarten Litmaath
  132. X              (maart@cs.vu.nl)
  133. X
  134. X
  135. XConsider a setuid root shell script written in Bourne shell command language
  136. Xand called `/bin/powerful'.
  137. XThe first line of the script will be (without indentation!):
  138. X
  139. X    #!/bin/sh
  140. X
  141. XIf it doesn't begin with such a line, it's no use to chmod it to 6755 or
  142. Xwhatever, because in that case it's just a shell script of the `old' kind:
  143. Xthe Bourne shell receives an exec format error when trying to execute it, and
  144. Xdecides it must be a shell script, so it forks a subshell with the script name
  145. Xas argument, to indicate from which file the commands are to be read.
  146. XShell scripts of the `new' kind are treated as follows: the kernel discovers
  147. Xthe magic number `#!' and tries to execute the command interpreter pointed out,
  148. Xwhich may be followed in the script by 1 argument.
  149. XBefore the exec of the interpreter the uid and gid fields somewhere in the user
  150. Xstructure of the process are filled in.
  151. XSetuid script scheme (kernel manipulations faked by C routines):
  152. X
  153. X    execl("/bin/powerful", "powerful", (char *) 0);
  154. X
  155. X      |
  156. X      V
  157. X
  158. X    setuid(0);
  159. X    setgid(0);      /* possibly */
  160. X    execl("/bin/sh", "sh", "/bin/powerful", (char *) 0);
  161. X
  162. XNow, what if the name of the very shell script were e.g. "-i"? Wouldn't that
  163. Xgive a nice exec?
  164. X
  165. X    execl("/bin/sh", "sh", "-i", (char *) 0);
  166. X
  167. XSo link the script to a file named "-i", and voila!
  168. XYes, one needs write permission somewhere on the same device, if one's
  169. Xoperating system doesn't support symbolic links.
  170. X
  171. XWhat about the csh command interpreter? Well, 4.2BSD provides us with a csh
  172. Xwhich has a NEW option: "-b"! Its goal is to avoid just the thing described
  173. Xabove: the mnemonic for `b' is `break'; this option prevents following
  174. Xarguments of an exec of /bin/csh from being interpreted as options...
  175. XThe csh refuses to run a setuid shell script unless the option is present...
  176. XScheme:
  177. X    #!/bin/csh -b
  178. X    ...
  179. X
  180. X    execl("-i", "unimportant", (char *) 0);
  181. X
  182. X      |
  183. X      V
  184. X
  185. X    setuid(0);
  186. X    setgid(0);
  187. X    execl("/bin/csh", "csh", "-b", "-i", (char *) 0);
  188. X
  189. XAnd indeed the contents of the file "-i" are executed!
  190. XHowever, there's still another bug hidden, albeit not for long!
  191. XWhat if I could `get between' the setuid()/setgid() and the open() of the
  192. Xcommand file by the command interpreter?
  193. XIn that case I could unlink() my link to the setuid shell script, and quickly
  194. Xlink() some other shell script into its place, couldn't I?
  195. XRight.
  196. XYet another source of trouble for /bin/sh setuid scripts is the reputed IFS
  197. Xshell variable. Of course there's also the PATH variable, which might cause
  198. Xproblems. However, one can circumvent these 2 jokers easily.
  199. XA solution to the link()/unlink() problems would be the specification of the
  200. Xfull path of the script in the script itself:
  201. X
  202. X    #!/bin/sh /etc/setuid_script
  203. X    shift        # remove the `extra' argument
  204. X    ...
  205. X
  206. XSome objections:
  207. X1)
  208. X    currently the total length of shell + argument mustn't exceed 32 chars
  209. X    (easily fixed);
  210. X2)
  211. X    4.[23]BSD csh is expecting a `-b' flag as the first argument, instead
  212. X    of the full path (easily fixed);
  213. X3)
  214. X    the interpreter gets an extra argument;
  215. X4)
  216. X    the difficulty of maintaining setuid shell scripts increases - if one
  217. X    moves a script, one shouldn't forget to edit it... - editing in turn
  218. X    could turn off the setuid bits, so one shouldn't forget to chmod(1)
  219. X    the file `back'... - conceptually the solution above isn't `elegant'.
  220. X
  221. XHow does indir(1) tackle the problems? The script to be executed will look
  222. Xlike:
  223. X    #!/bin/indir -u
  224. X    #?/bin/sh /etc/setuid_script
  225. X    ...
  226. X
  227. XIndir(1) will try to open the script and read the `#?' line indicating the
  228. Xreal interpreter and a safe (absolute) pathname of the script. But remember:
  229. Xthe link to the script might have been quickly replaced with a link to another
  230. Xscript, i.e. how can we trust this `#?' line?
  231. XAnswer: if and only if the file we're reading from is SETUID (setgid) to the
  232. XEFFECTIVE uid (gid) of the process, we know we're executing the original
  233. Xscript (to be 100% correct: the original link might have been replaced with a
  234. Xlink to ANOTHER setuid script of the same owner -> merely a waste of time).
  235. XTo reliably check the condition stated above, we use fstat(2) on the file
  236. Xdescriptor we're reading from. Can you figure out why stat(2) would be
  237. Xinsecure?
  238. XTo deal with IFS, PATH and other environment problems, indir(1) resets the
  239. Xenvironment to a simple default:
  240. X
  241. X    PATH=/bin:/usr/bin:/usr/ucb
  242. X
  243. XWhen you need e.g. $HOME, you should get it from /etc/passwd instead of
  244. Xtrusting what the environment says. Of course with indir(1) problem 4 remains.
  245. X
  246. X                --------
  247. + END-OF-FILE setuid.txt
  248. chmod 'u=rw,g=r,o=r' 'setuid.txt'
  249. set `wc -c 'setuid.txt'`
  250. count=$1
  251. case $count in
  252. 4577)    :;;
  253. *)    echo 'Bad character count in ''setuid.txt' >&2
  254.         echo 'Count should be 4577' >&2
  255. esac
  256. echo Extracting 'indir.1'
  257. sed 's/^X//' > 'indir.1' << '+ END-OF-FILE ''indir.1'
  258. X.\" maart@cs.vu.nl - Maarten Litmaath Wed Nov  1 07:16:05 MET 1989
  259. X.\" uses -man
  260. X.TH INDIR 1 "Nov 01, 1989"
  261. X.UC 4
  262. X.SH NAME
  263. X.B indir
  264. X\- run (setuid/setgid) (shell) scripts indirectly
  265. X.SH SYNOPSIS
  266. X.B "#!/bin/indir \-ugbn"
  267. X.br
  268. X.B #?...
  269. X.br
  270. X.SH DESCRIPTION
  271. X.I Indir
  272. Xis not executed from a shell normally. Instead it can be used
  273. Xas the interpreter for shell scripts that:
  274. X.PP
  275. X.RS
  276. X1) need to be run \fIsetuid\fR (\fIsetgid\fR) to someone else, or
  277. X.PP
  278. X2) fail to meet the constraints for a `\fB#!\fR' line (see \fIexecve\fR(2)).
  279. X.RE
  280. X.PP
  281. X.I Indir
  282. Xis invoked by making the first line of the script
  283. X.PP
  284. X.RS
  285. X.B "#!/bin/indir \-ugbn"
  286. X.RE
  287. X.PP
  288. Xrather than the usual
  289. X.PP
  290. X.RS
  291. X.B #!/bin/sh
  292. X.RE
  293. X.PP
  294. XExactly 1 of the options must be specified (see below).
  295. X.I Indir
  296. Xtries to open the script for reading. If successful it discards the first
  297. Xline (containing `\fB#!/bin/indir \-ugbn\fR') and tries to read a line
  298. Xformatted as follows:
  299. X.PP
  300. X.RS
  301. X.ft B
  302. X#?absolute\-path\-of\-interpreter arguments
  303. X.RE
  304. X.PP
  305. XWhitespace around the `\fB#?\fR' \fImagic number\fR is discarded.
  306. XThe interpreter as well as the arguments are subject to
  307. Xthe `\fItilde convention\fR': a leading string `\fI~user\fR' is expanded to
  308. Xthe home directory of `\fIuser\fR', where `\fIuser\fR' is the longest
  309. Xstring of \fIlogin-name characters\fR immediately following the `~'.
  310. XIf this string equals the null string, the login name of the REAL uid
  311. Xis used.  Currently \fIlogin-name characters\fR are defined to include every
  312. Xcharacter except the following: `/', space, horizontal tab, newline and NUL.
  313. X.PP
  314. XFurthermore an argument consisting of a single `%' is expanded to the name
  315. Xof the script, if and only if the `\-\fIn\fR' option has been specified, else
  316. Xthe expansion is inhibited (see below).
  317. X.PP
  318. XThe `\-\fIn\fR' option turns off security checking: it must be used only for
  319. Xscripts that are not setuid or setgid. For scripts that are only setuid the
  320. X`\-\fIu\fR' option must be used. The `\-\fIg\fR' option is for scripts that
  321. Xare only setgid. Finally the `\-\fIb\fR' option is for scripts that are both
  322. Xsetuid and setgid.  Examples:
  323. X.PP
  324. X.RS
  325. X.ft B
  326. X#!/bin/indir \-u
  327. X.br
  328. X#?/bin/csh \-bf /usr/etc/setuid_script \-v
  329. X.sp
  330. X#!/bin/indir \-g
  331. X.br
  332. X#?~my_name/bin/prog \-f ~my_name/lib/setgid_script
  333. X.sp
  334. X#!/bin/indir \-n
  335. X.br
  336. X#?sed \-n \-f %
  337. X.RE
  338. X.PP
  339. XA `\fB#?\fR' line is limited to 256 characters. However,
  340. Xif the line ends in a backslash (`\fB\\\fR'), the next line is assumed to
  341. Xcontain further arguments after a mandatory leading `\fB#?\fR', and so on.
  342. XThere is a system-imposed limit on the total number of characters present
  343. Xin the argument list.
  344. X.PP
  345. XTo avoid `linking tricks' through which uncontrolled privileges of the
  346. Xowner of the file could be acquired, 3 measures have been taken for setuid
  347. X(setgid) scripts:
  348. X.RS
  349. X.PP
  350. X1) the script must contain its own safe invocation, that is the
  351. X`\fB#?\fR' line; `%' arguments will cause \fIindir\fR to abort;
  352. X.PP
  353. X2) the \fIenvironment\fR is reset to a simple default:
  354. X.PP
  355. X.RS
  356. X.B PATH=/bin:/usr/bin:/usr/ucb
  357. X.RE
  358. X.PP
  359. X3) before the final \fIexecve\fR(2)
  360. X.I indir
  361. Xchecks if the owner and mode of the script are still what they are supposed
  362. Xto be (using \fIfstat\fR(2)); if there is a discrepancy,
  363. X.I indir
  364. Xwill abort with an error message.
  365. X.RE
  366. X.PP
  367. X.I Indir
  368. Xwill only exec a pathname beginning with a `\fB/\fR', unless the `\-\fIn\fR'
  369. Xoption was specified. In the latter case the user's PATH will be searched
  370. X(if necessary) to locate the interpreter (see \fIexecvp\fR(3)). Of course
  371. X.I indir
  372. Xcan be `fooled' by supplying dubious arguments to the interpreter,
  373. Xlike \fIrelative pathnames\fR. Furthermore it is a mistake to let any of
  374. Xthe directory components of an ultimate path be writable by others.
  375. XIn our first example `/', `/\fIbin\fR', `/\fIusr\fR' and `/\fIusr/etc\fR'
  376. Xshould not be writable for ordinary users.
  377. X.SH AUTHOR
  378. XMaarten Litmaath @ VU Informatika Amsterdam (maart@cs.vu.nl)
  379. X.SH "SEE ALSO"
  380. X.B "sh(1), csh(1)"
  381. X.SH BUGS
  382. XThe maintenance of setuid (setgid) scripts is a bit annoying: if a script is
  383. Xmoved, one must not forget to change the path mentioned in the script.
  384. XPossibly the editing causes a setuid bit to get turned off.
  385. + END-OF-FILE indir.1
  386. chmod 'u=rw,g=r,o=r' 'indir.1'
  387. set `wc -c 'indir.1'`
  388. count=$1
  389. case $count in
  390. 4112)    :;;
  391. *)    echo 'Bad character count in ''indir.1' >&2
  392.         echo 'Count should be 4112' >&2
  393. esac
  394. echo Extracting 'Makefile'
  395. sed 's/^X//' > 'Makefile' << '+ END-OF-FILE ''Makefile'
  396. XSRCS        = indir.c error.c
  397. XOBJS        = indir.o error.o
  398. XMAN        = indir.1
  399. X
  400. X# a few defines for testing purposes; if you test in the current
  401. X# directory the paths needn't be absolute
  402. X# the path of indir will be used in #! lines -> max. 32 characters for
  403. X# #! + name + mandatory option
  404. XPATH_OF_INDIR    = ./indir
  405. XTEST_DIRECTORY    = .
  406. X
  407. XCC        = cc
  408. X# if your C library doesn't have strrchr()
  409. X# STRRCHR    = -Dstrrchr=rindex
  410. X
  411. XCFLAGS        = -O $(STRRCHR)
  412. X
  413. Xindir:        exec_ok $(OBJS)
  414. X        $(CC) -O -o indir $(OBJS)
  415. X
  416. Xexec_ok:
  417. X        exec_test
  418. X
  419. Xtest:        exec_ok
  420. X        test -f tests_made || PATH_OF_INDIR=$(PATH_OF_INDIR) \
  421. X            TEST_DIRECTORY=$(TEST_DIRECTORY) make_tests
  422. X        do_tests
  423. X
  424. Xlint:
  425. X        lint $(SRCS)
  426. X
  427. Xman:
  428. X        nroff -man $(MAN) > indir.man
  429. X
  430. Xshar:
  431. X        shar README Intro setuid.txt $(MAN) Makefile $(SRCS) indir.h \
  432. X            error.h my_varargs.h exec_test make_tests do_tests \
  433. X            file_argument > indir.sh
  434. X
  435. Xclean:
  436. X        /bin/rm -f *.o indir.man wrong.* ok.* core exec_ok tests_made
  437. X
  438. Xindir.o:    indir.c indir.h error.h my_varargs.h
  439. Xerror.o:    error.c error.h my_varargs.h
  440. + END-OF-FILE Makefile
  441. chmod 'u=rw,g=r,o=r' 'Makefile'
  442. set `wc -c 'Makefile'`
  443. count=$1
  444. case $count in
  445. 1002)    :;;
  446. *)    echo 'Bad character count in ''Makefile' >&2
  447.         echo 'Count should be 1002' >&2
  448. esac
  449. echo Extracting 'indir.c'
  450. sed 's/^X//' > 'indir.c' << '+ END-OF-FILE ''indir.c'
  451. Xstatic    char    sccsid[] = "@(#)indir.c 2.0 89/11/01 Maarten Litmaath";
  452. X
  453. X/*
  454. X * indir.c
  455. X * execute (setuid/setgid) (shell) scripts indirectly
  456. X * see indir.1
  457. X */
  458. X
  459. X#include    "indir.h"
  460. X#include    "error.h"
  461. X
  462. X
  463. Xstatic    char    *Env[] = {
  464. X    "PATH=/bin:/usr/bin:/usr/ucb",
  465. X    0
  466. X};
  467. Xstatic    char    *Prog, *File, *Interpreter, *Newargv[MAXARGV];
  468. Xstatic    int    Uid_check = 1, Gid_check = 1;
  469. Xstatic    uid_t    Uid;
  470. X
  471. X
  472. Xmain(argc, argv)
  473. Xint    argc;
  474. Xchar    **argv;
  475. X{
  476. X    extern    char    *strrchr();
  477. X    extern    int    execve(), execvp();
  478. X    FILE    *fp;
  479. X    int    c;
  480. X    struct    stat    st;
  481. X
  482. X
  483. X    if (!(Prog = strrchr(argv[0], '/')))
  484. X        Prog = argv[0];
  485. X    else
  486. X        ++Prog;
  487. X
  488. X    if (!argv[1] || *argv[1] != '-' || !argv[1][1] || argv[1][2])
  489. X        error(E_opt, Prog);
  490. X
  491. X    switch (argv[1][1]) {
  492. X    case 'n':
  493. X        Uid_check = Gid_check = 0;
  494. X        break;
  495. X    case 'g':
  496. X        Uid_check = 0;
  497. X        break;
  498. X    case 'u':
  499. X        Gid_check = 0;
  500. X        break;
  501. X    case 'b':
  502. X        break;
  503. X    default:
  504. X        error(E_opt, Prog);
  505. X        break;
  506. X    }
  507. X
  508. X    if (argc < 3)
  509. X        error(E_file, Prog);
  510. X
  511. X    File = argv[2];
  512. X
  513. X    if (!(fp = fopen(File, "r")))
  514. X        error(E_open, Prog, File, geterr());
  515. X
  516. X    Uid = getuid();
  517. X
  518. X    while ((c = getc(fp)) != '\n' && c != EOF)    /* skip #! line */
  519. X        ;
  520. X
  521. X    if (!(c = getnewargv(fp))) {
  522. X        if (ferror(fp))
  523. X            error(E_read, Prog, File, geterr());
  524. X        error(E_fmt, Prog, File);
  525. X    }
  526. X
  527. X    argv += 3;            /* skip Prog, option and File */
  528. X
  529. X    while (*argv && c < MAXARGV - 1)
  530. X        Newargv[c++] = *argv++;
  531. X
  532. X    if (*argv)
  533. X        error(E_args, Prog, File);
  534. X
  535. X    Newargv[c] = 0;
  536. X
  537. X#ifdef    DEBUG
  538. X    fprintf(stderr, "Interpreter=`%s'\n", Interpreter);
  539. X    for (c = 0; Newargv[c]; c++)
  540. X        fprintf(stderr, "Newargv[%d]=`%s'\n", c, Newargv[c]);
  541. X#endif    /* DEBUG */
  542. X
  543. X    if (fstat(fileno(fp), &st) != 0)
  544. X        error(E_fstat, Prog, File, geterr());
  545. X    /*
  546. X     * List of possible Uid_check/setuid combinations:
  547. X     *
  548. X     * !Uid_check !setuid -> OK: ordinary script
  549. X     * !Uid_check  setuid -> security hole: checking should be enabled
  550. X     *  Uid_check !setuid -> fake
  551. X     *  Uid_check  setuid -> check if st_uid == euid
  552. X     */
  553. X
  554. X    if (!Uid_check) {
  555. X        /*
  556. X         * If the file is setuid, consistency should be checked;
  557. X         * however, checking has been disabled for this file (leading
  558. X         * to security holes again!), so warn the user and exit.
  559. X         */
  560. X        if (st.st_mode & S_ISUID)
  561. X            error(E_mode, Prog, File);
  562. X    } else {
  563. X        /*
  564. X         * Check if the file we're reading from is setuid and owned
  565. X         * by geteuid().  If this test fails, the file is a fake,
  566. X         * else it MUST be ok!
  567. X         */
  568. X        if (!(st.st_mode & S_ISUID) || st.st_uid != geteuid())
  569. X            error(E_alarm, Prog, File);
  570. X    }
  571. X
  572. X    /* setgid checks */
  573. X
  574. X    if (!Gid_check) {
  575. X        if (st.st_mode & S_ISGID)
  576. X            error(E_mode, Prog, File);
  577. X    } else {
  578. X        if (!(st.st_mode & S_ISGID) || st.st_gid != getegid())
  579. X            error(E_alarm, Prog, File);
  580. X    }
  581. X
  582. X    /*
  583. X     * If we're executing a set[ug]id file, replace the complete
  584. X     * environment by a save default, else permit the PATH to be
  585. X     * searched too.
  586. X     */
  587. X    if (st.st_mode & (S_ISUID | S_ISGID))
  588. X        execve(Interpreter, Newargv, Env);
  589. X    else
  590. X        execvp(Interpreter, Newargv);
  591. X
  592. X    error(E_exec, Prog, Interpreter, File, geterr());
  593. X}
  594. X
  595. X
  596. Xstatic    int    getnewargv(fp)
  597. XFILE    *fp;
  598. X{
  599. X    static    char    buf[MAXLEN];
  600. X    char    *skipblanks(), *skiptoblank(), *strrchr(), *malloc(), *tilde();
  601. X    register char    *p;
  602. X    register int    i = 0;
  603. X
  604. X
  605. X    for (;;) {
  606. X        if (!fgets(buf, sizeof buf, fp) || !*buf
  607. X            || buf[strlen(buf) - 1] != '\n')
  608. X            return 0;
  609. X
  610. X        p = skipblanks(buf);
  611. X
  612. X        switch (*p++) {
  613. X        case '\0':            /* skip empty lines */
  614. X            continue;
  615. X        case COMMENT:
  616. X            break;
  617. X        default:
  618. X            return 0;
  619. X        }
  620. X
  621. X        if (*p++ == MAGIC)        /* skip ordinary comments */
  622. X            break;
  623. X    }
  624. X
  625. X    p = skipblanks(p);
  626. X
  627. X    if (*p == TILDE)
  628. X        p = tilde(p, &Interpreter);
  629. X    else {
  630. X        if (*p != '/') {
  631. X            if (!*p)
  632. X                return 0;
  633. X            if (Uid_check || Gid_check)
  634. X                error(E_name, Prog, File);
  635. X        }
  636. X        Interpreter = p;
  637. X        p = skiptoblank(p);
  638. X        *p++ = '\0';
  639. X    }
  640. X
  641. X    if (!(Newargv[0] = strrchr(Interpreter, '/')))
  642. X        Newargv[0] = Interpreter;
  643. X    else
  644. X        ++Newargv[0];
  645. X
  646. X    while (i < MAXARGV - 2) {
  647. X        p = skipblanks(p);
  648. X
  649. X        if (!*p)
  650. X            break;
  651. X
  652. X        if (*p == '\\' && p[1] == '\n') {
  653. X            if (!(p = malloc(MAXLEN)))
  654. X                error(E_mem, Prog, File, geterr());
  655. X            if (!fgets(p, MAXLEN, fp) || !*p
  656. X                || p[strlen(p) - 1] != '\n')
  657. X                return 0;
  658. X            p = skipblanks(p);
  659. X            if (*p++ != COMMENT || *p++ != MAGIC)
  660. X                return 0;
  661. X            continue;
  662. X        }
  663. X
  664. X        if (*p == TILDE)
  665. X            p = tilde(p, &Newargv[++i]);
  666. X        else if (*p++ == SUBST
  667. X            && (*p == '\n' || *p == ' ' || *p == '\t')) {
  668. X            if (Uid_check || Gid_check)
  669. X                error(E_subst, Prog, SUBST, File);
  670. X            Newargv[++i] = File;
  671. X        } else {
  672. X            Newargv[++i] = --p;
  673. X            p = skiptoblank(p);
  674. X            *p++ = '\0';
  675. X        }
  676. X    }
  677. X
  678. X    if (*p)
  679. X        error(E_args, Prog, File);
  680. X
  681. X    Newargv[++i] = 0;
  682. X    return i;
  683. X}
  684. X
  685. X
  686. Xstatic    char    *skipblanks(p)
  687. Xregister char    *p;
  688. X{
  689. X    register int    c;
  690. X
  691. X    while ((c = *p++) == ' ' || c == '\t' || c == '\n')
  692. X        ;
  693. X    return --p;
  694. X}
  695. X
  696. X
  697. Xstatic    char    *skiptoblank(p)
  698. Xregister char    *p;
  699. X{
  700. X    register int    c;
  701. X
  702. X    while ((c = *p++) != ' ' && c != '\t' && c != '\n')
  703. X        ;
  704. X    return --p;
  705. X}
  706. X
  707. X
  708. X#include    <pwd.h>
  709. X
  710. X
  711. Xstatic    char    *tilde(p, pp)
  712. Xregister char    *p;
  713. Xchar    **pp;
  714. X{
  715. X    register char    *q, c;
  716. X    struct    passwd    *pw, *getpwuid(), *getpwnam();
  717. X    char    *orig, save, *buf, *strcpy(), *strncpy(), *malloc();
  718. X    int    n;
  719. X
  720. X
  721. X    orig = p++;
  722. X    for (q = p; (c = *q++) != '/' && c != '\n' && c != ' ' && c != '\t'; )
  723. X        ;
  724. X    if (--q == p)
  725. X        pw = getpwuid(Uid);
  726. X    else {
  727. X        save = *q;
  728. X        *q = '\0';
  729. X        pw = getpwnam(p);
  730. X        *(p = q) = save;
  731. X    }
  732. X    q = skiptoblank(q);
  733. X    save = *q;
  734. X    *q = '\0';
  735. X    if (!pw)
  736. X        error(E_tilde, Prog, orig, File);
  737. X    if (!(buf = malloc((n = strlen(pw->pw_dir)) + q - p + 1)))
  738. X        error(E_mem, Prog, File, geterr());
  739. X    strcpy(buf, pw->pw_dir);
  740. X    strcpy(buf + n, p);
  741. X    *pp = buf;
  742. X    *q = save;
  743. X    return q;
  744. X}
  745. + END-OF-FILE indir.c
  746. chmod 'u=rw,g=r,o=r' 'indir.c'
  747. set `wc -c 'indir.c'`
  748. count=$1
  749. case $count in
  750. 5483)    :;;
  751. *)    echo 'Bad character count in ''indir.c' >&2
  752.         echo 'Count should be 5483' >&2
  753. esac
  754. echo Extracting 'error.c'
  755. sed 's/^X//' > 'error.c' << '+ END-OF-FILE ''error.c'
  756. X/*
  757. X * error.c
  758. X */
  759. X#define EXTERN
  760. X#include    "error.h"
  761. X#include    "my_varargs.h"
  762. X#include    <stdio.h>
  763. X
  764. X/* VARARGS1 */
  765. XVARARGS(void, error, (error_p_type error_p, ...))
  766. X{
  767. X#ifndef __STDC__
  768. X    error_p_type    error_p;
  769. X#endif    /* !__STDC__ */
  770. X
  771. X    va_list ap;
  772. X
  773. X    VA_START(ap, error_p_type, error_p);
  774. X    vfprintf(stderr, error_p->message, ap);
  775. X    va_end(ap);
  776. X    exit(error_p->status);
  777. X}
  778. X
  779. X
  780. Xchar    *geterr()
  781. X{
  782. X    extern    int    errno, sys_nerr;
  783. X    extern    char    *sys_errlist[];
  784. X    static    char    s[64];
  785. X
  786. X    if ((unsigned) errno < sys_nerr)
  787. X        return sys_errlist[errno];
  788. X    (void) sprintf(s, "Unknown error %d", errno);
  789. X    return s;
  790. X}
  791. + END-OF-FILE error.c
  792. chmod 'u=rw,g=r,o=r' 'error.c'
  793. set `wc -c 'error.c'`
  794. count=$1
  795. case $count in
  796. 580)    :;;
  797. *)    echo 'Bad character count in ''error.c' >&2
  798.         echo 'Count should be 580' >&2
  799. esac
  800. echo Extracting 'indir.h'
  801. sed 's/^X//' > 'indir.h' << '+ END-OF-FILE ''indir.h'
  802. X#include    <sys/param.h>
  803. X#include    <sys/stat.h>
  804. X#include    <stdio.h>
  805. X
  806. X#define        COMMENT        '#'
  807. X#define        MAGIC        '?'
  808. X#define        TILDE        '~'
  809. X#define        SUBST        '%'
  810. X#define        MAXLEN        256
  811. X#define        MAXARGV        1024
  812. X
  813. X#ifdef    NCARGS
  814. X#if NCARGS < MAXARGV
  815. X#undef    MAXARGV
  816. X#define        MAXARGV        NCARGS
  817. X#endif    /* NCARGS < MAXARGV */
  818. X#endif    /* NCARGS */
  819. + END-OF-FILE indir.h
  820. chmod 'u=rw,g=r,o=r' 'indir.h'
  821. set `wc -c 'indir.h'`
  822. count=$1
  823. case $count in
  824. 317)    :;;
  825. *)    echo 'Bad character count in ''indir.h' >&2
  826.         echo 'Count should be 317' >&2
  827. esac
  828. echo Extracting 'error.h'
  829. sed 's/^X//' > 'error.h' << '+ END-OF-FILE ''error.h'
  830. X/*
  831. X * error.h
  832. X */
  833. X
  834. X#ifndef ERROR_H
  835. X#define ERROR_H
  836. X
  837. Xstruct    error_s {
  838. X    int    status;
  839. X    char    *message;
  840. X};
  841. Xtypedef struct    error_s *error_p_type;
  842. X
  843. X#include    "my_varargs.h"
  844. XEXTERN_VARARGS(void, error, (error_p_type error, ...));
  845. Xextern    char    *geterr();
  846. X
  847. X#ifndef EXTERN
  848. X#define EXTERN    extern
  849. X#define INIT_ERROR_S(value, message)
  850. X#else    /* !EXTERN */
  851. X#define INIT_ERROR_S(value, message)    = { { value, message } }
  852. X#endif    /* !EXTERN */
  853. X
  854. X#define ERROR(error, value, message)    \
  855. X    EXTERN    struct    error_s error[] INIT_ERROR_S(value, message);
  856. X
  857. XERROR(E_opt,    1, "%s: -[ugbn] option expected\n")
  858. XERROR(E_file,   2, "%s: file argument expected\n")
  859. XERROR(E_open,   3, "%s: cannot open `%s': %s\n")
  860. XERROR(E_name,   4, "%s: pathname of interpreter in `%s' is not absolute\n")
  861. XERROR(E_mem,    5, "%s: malloc error for `%s': %s\n")
  862. XERROR(E_subst,  6,
  863. X    "%s: `%c' substitution attempt under -[ugb] option in file `%s'\n")
  864. XERROR(E_args,   7, "%s: too many arguments for `%s'\n")
  865. XERROR(E_read,   8, "%s: read error in `%s': %s\n")
  866. XERROR(E_fmt,    9, "%s: format error in `%s'\n")
  867. XERROR(E_tilde, 10, "%s: cannot resolve `%s' in `%s'\n")
  868. XERROR(E_fstat, 11, "%s: cannot fstat `%s': %s\n")
  869. XERROR(E_mode,  12, "%s: `%s' is set[ug]id, yet has checking disabled!\n")
  870. XERROR(E_alarm, 13, "%s: `%s' is a fake!\n")
  871. XERROR(E_exec,  14, "%s: cannot execute `%s' in `%s': %s\n")
  872. X
  873. X#endif    /* !ERROR_H */
  874. + END-OF-FILE error.h
  875. chmod 'u=rw,g=r,o=r' 'error.h'
  876. set `wc -c 'error.h'`
  877. count=$1
  878. case $count in
  879. 1356)    :;;
  880. *)    echo 'Bad character count in ''error.h' >&2
  881.         echo 'Count should be 1356' >&2
  882. esac
  883. echo Extracting 'my_varargs.h'
  884. sed 's/^X//' > 'my_varargs.h' << '+ END-OF-FILE ''my_varargs.h'
  885. X/*
  886. X * my_varargs.h
  887. X */
  888. X#ifndef MY_VARARGS_H
  889. X#define MY_VARARGS_H
  890. X
  891. X#ifdef    __STDC__
  892. X
  893. X#define        EXTERN_VARARGS(type, f, args)    extern    type    f args
  894. X#define        VARARGS(type, f, args)        type    f args
  895. X#define        VA_START(ap, type, start)    va_start(ap, start)
  896. X#include    <stdarg.h>
  897. X
  898. X#else    /* __STDC__ */
  899. X
  900. X#define        EXTERN_VARARGS(type, f, args)    extern    type    f()
  901. X#define        VARARGS(type, f, args)        type    f(va_alist) \
  902. X                        va_dcl
  903. X#define        VA_START(ap, type, start)    va_start(ap); \
  904. X                        start = va_arg(ap, type)
  905. X#include    <varargs.h>
  906. X
  907. X#endif    /* __STDC__ */
  908. X
  909. X#endif    /* !MY_VARARGS_H */
  910. + END-OF-FILE my_varargs.h
  911. chmod 'u=rw,g=r,o=r' 'my_varargs.h'
  912. set `wc -c 'my_varargs.h'`
  913. count=$1
  914. case $count in
  915. 558)    :;;
  916. *)    echo 'Bad character count in ''my_varargs.h' >&2
  917.         echo 'Count should be 558' >&2
  918. esac
  919. echo Extracting 'exec_test'
  920. sed 's/^X//' > 'exec_test' << '+ END-OF-FILE ''exec_test'
  921. X#!/bin/sh
  922. X
  923. Xcat > try << EOF
  924. X#!/bin/test -f
  925. XEOF
  926. X
  927. Xchmod 100 try
  928. X
  929. X./try 2> /dev/null
  930. Xstatus=$?
  931. X/bin/rm -f try
  932. X
  933. Xcase $status in
  934. X0)
  935. X    > exec_ok
  936. X    exit 0
  937. Xesac
  938. X
  939. Xecho "Sorry, your system doesn't seem to"
  940. Xecho "recognize the #! magic number.  Abort."
  941. Xexit 1
  942. + END-OF-FILE exec_test
  943. chmod 'u=rwx,g=rx,o=rx' 'exec_test'
  944. set `wc -c 'exec_test'`
  945. count=$1
  946. case $count in
  947. 247)    :;;
  948. *)    echo 'Bad character count in ''exec_test' >&2
  949.         echo 'Count should be 247' >&2
  950. esac
  951. echo Extracting 'make_tests'
  952. sed 's/^X//' > 'make_tests' << '+ END-OF-FILE ''make_tests'
  953. X#!/bin/sh
  954. X: ${PATH_OF_INDIR?} ${TEST_DIRECTORY?}
  955. Xmyname=ok.01
  956. Xcat > $myname << EOF
  957. X#!$PATH_OF_INDIR -u
  958. X#?/bin/csh -bf $TEST_DIRECTORY/$myname -v
  959. Xwhoami
  960. Xprintenv
  961. Xecho args:
  962. Xforeach i (\$*)
  963. X    echo '    '\$i
  964. Xend
  965. XEOF
  966. Xchmod 4755 $myname
  967. X####################
  968. Xmyname=ok.02
  969. Xcat > $myname << EOF
  970. X#!$PATH_OF_INDIR -n
  971. X
  972. X#comment
  973. X#? /bin/sh \\
  974. X#? $TEST_DIRECTORY/$myname x y \\
  975. X#? z
  976. X
  977. Xwhoami
  978. Xprintenv
  979. Xecho args:
  980. Xfor i
  981. Xdo
  982. X    echo '    '\$i
  983. Xdone
  984. XEOF
  985. Xchmod 755 $myname
  986. X####################
  987. Xmyname=ok.03
  988. Xcat > $myname << EOF
  989. X#!$PATH_OF_INDIR -g
  990. X   #? ~root/bin/echo ~ ~/foo ~bin/bar ~/bin/baz
  991. X
  992. Xthis_will_not_get_executed
  993. XEOF
  994. Xchmod 2755 $myname
  995. X####################
  996. Xmyname=ok.04
  997. Xcat > $myname << EOF
  998. X#!$PATH_OF_INDIR -n
  999. X#?sed -n -f %
  1000. X/uu/p
  1001. XEOF
  1002. Xchmod 755 $myname
  1003. X####################
  1004. Xmyname=ok.05
  1005. Xcat > $myname << EOF
  1006. X#!$PATH_OF_INDIR -n
  1007. X#?sed -n -f \\
  1008. X#? \\
  1009. X#? % \\
  1010. X#?
  1011. X/uu/p
  1012. XEOF
  1013. Xchmod 755 $myname
  1014. X####################
  1015. Xmyname=wrong.01
  1016. Xcat > $myname << EOF
  1017. X#!$PATH_OF_INDIR
  1018. X#?/bin/echo you_should_not_see_this
  1019. XEOF
  1020. Xchmod 755 $myname
  1021. X####################
  1022. Xmyname=wrong.02
  1023. Xcat > $myname << EOF
  1024. X#!$PATH_OF_INDIR -n
  1025. X#?/bin/echo you_should_not_see_this \\
  1026. XEOF
  1027. Xchmod 755 $myname
  1028. X####################
  1029. Xmyname=wrong.03
  1030. Xcat > $myname << EOF
  1031. X#!$PATH_OF_INDIR -n
  1032. Xhello
  1033. X#?/bin/echo you_should_not_see_this
  1034. XEOF
  1035. Xchmod 755 $myname
  1036. X####################
  1037. Xmyname=wrong.04
  1038. Xcat > $myname << EOF
  1039. X#!$PATH_OF_INDIR -n
  1040. X#?/bin/echo you_should_not_see_this \\
  1041. X#comment
  1042. X#? %
  1043. XEOF
  1044. Xchmod 755 $myname
  1045. X####################
  1046. Xmyname=wrong.05
  1047. Xcat > $myname << EOF
  1048. X#!$PATH_OF_INDIR -n
  1049. X#hello
  1050. X#?/bin/echo you_should_not_see_this ~nonexistent/bin/bletch
  1051. XEOF
  1052. Xchmod 755 $myname
  1053. X####################
  1054. Xmyname=wrong.06
  1055. Xcat > $myname << EOF
  1056. X#!$PATH_OF_INDIR -u
  1057. X#?/ you_should_not_see_this
  1058. XEOF
  1059. Xchmod 4755 $myname
  1060. X####################
  1061. Xmyname=wrong.07
  1062. Xcat > $myname << EOF
  1063. X#!$PATH_OF_INDIR -u
  1064. X#?/bin/echo you_should_not_see_this %
  1065. XEOF
  1066. Xchmod 755 $myname
  1067. X####################
  1068. Xmyname=wrong.08
  1069. Xcat > $myname << EOF
  1070. X#!$PATH_OF_INDIR -g
  1071. X#?../bin/echo you_should_not_see_this
  1072. XEOF
  1073. Xchmod 755 $myname
  1074. X####################
  1075. Xmyname=wrong.09
  1076. Xcat > $myname << EOF
  1077. X#!$PATH_OF_INDIR -g
  1078. X#?/bin/echo you_should_not_see_this
  1079. XEOF
  1080. Xchmod 4755 $myname
  1081. X####################
  1082. Xmyname=wrong.10
  1083. Xcat > $myname << EOF
  1084. X#!$PATH_OF_INDIR -u
  1085. X#?/bin/echo you_should_not_see_this
  1086. XEOF
  1087. Xchmod 2755 $myname
  1088. X####################
  1089. Xmyname=wrong.11
  1090. Xcat > $myname << EOF
  1091. X#!$PATH_OF_INDIR -n
  1092. X#?/bin/echo you_should_not_see_this
  1093. XEOF
  1094. Xchmod 2755 $myname
  1095. X####################
  1096. X> tests_made
  1097. + END-OF-FILE make_tests
  1098. chmod 'u=rwx,g=rx,o=rx' 'make_tests'
  1099. set `wc -c 'make_tests'`
  1100. count=$1
  1101. case $count in
  1102. 2453)    :;;
  1103. *)    echo 'Bad character count in ''make_tests' >&2
  1104.         echo 'Count should be 2453' >&2
  1105. esac
  1106. echo Extracting 'do_tests'
  1107. sed 's/^X//' > 'do_tests' << '+ END-OF-FILE ''do_tests'
  1108. X#!/bin/sh
  1109. Xfor i in wrong.* ok.*
  1110. Xdo
  1111. X    echo '' >&2
  1112. X    echo "$ ls -l $i" >&2
  1113. X    ls -l $i >&2
  1114. X    echo "$ cat $i" >&2
  1115. X    cat $i >&2
  1116. X    echo $
  1117. X    echo -n "-- hit return to run $i: " >&2
  1118. X    read x
  1119. X    $i file_argument
  1120. X    echo -n "-- hit return to continue: " >&2
  1121. X    read x
  1122. Xdone
  1123. + END-OF-FILE do_tests
  1124. chmod 'u=rwx,g=rx,o=rx' 'do_tests'
  1125. set `wc -c 'do_tests'`
  1126. count=$1
  1127. case $count in
  1128. 249)    :;;
  1129. *)    echo 'Bad character count in ''do_tests' >&2
  1130.         echo 'Count should be 249' >&2
  1131. esac
  1132. echo Extracting 'file_argument'
  1133. sed 's/^X//' > 'file_argument' << '+ END-OF-FILE ''file_argument'
  1134. Xroot:0sW84d7bh.QOc:0:1:Operator:/:/bin/csh
  1135. Xnobody:*:-2:-2::/:
  1136. Xdaemon:*:1:1::/:
  1137. Xsys:*:2:2::/:/bin/csh
  1138. Xbin:*:3:3::/bin:
  1139. Xuucp:*:4:4::/var/spool/uucppublic:
  1140. Xnews:*:6:6::/var/spool/news:/bin/csh
  1141. Xingres:*:7:7::/usr/ingres:/bin/csh
  1142. Xaudit:*:9:9::/etc/security/audit:/bin/csh
  1143. Xsync::1:1::/:/bin/sync
  1144. Xsysdiag:LTmKcPBITIe/M:0:1:System Diagnostic:/usr/diag/sysdiag:/usr/diag/sysdiag/sysdiag
  1145. X+:
  1146. + END-OF-FILE file_argument
  1147. chmod 'u=rw,g=r,o=r' 'file_argument'
  1148. set `wc -c 'file_argument'`
  1149. count=$1
  1150. case $count in
  1151. 381)    :;;
  1152. *)    echo 'Bad character count in ''file_argument' >&2
  1153.         echo 'Count should be 381' >&2
  1154. esac
  1155. exit 0
  1156.  
  1157. -- 
  1158. Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
  1159. Use a domain-based address or give alternate paths, or you may lose out.
  1160.