home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume21 / indir < prev    next >
Text File  |  1990-03-22  |  30KB  |  1,152 lines

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