Home | History | Annotate | Download | only in Misc
      1 /*
      2    Template for a setuid program that calls a script.
      3 
      4    The script should be in an unwritable directory and should itself
      5    be unwritable.  In fact all parent directories up to the root
      6    should be unwritable.  The script must not be setuid, that's what
      7    this program is for.
      8 
      9    This is a template program.  You need to fill in the name of the
     10    script that must be executed.  This is done by changing the
     11    definition of FULL_PATH below.
     12 
     13    There are also some rules that should be adhered to when writing
     14    the script itself.
     15 
     16    The first and most important rule is to never, ever trust that the
     17    user of the program will behave properly.  Program defensively.
     18    Check your arguments for reasonableness.  If the user is allowed to
     19    create files, check the names of the files.  If the program depends
     20    on argv[0] for the action it should perform, check it.
     21 
     22    Assuming the script is a Bourne shell script, the first line of the
     23    script should be
     24     #!/bin/sh -
     25    The - is important, don't omit it.  If you're using esh, the first
     26    line should be
     27     #!/usr/local/bin/esh -f
     28    and for ksh, the first line should be
     29     #!/usr/local/bin/ksh -p
     30    The script should then set the variable IFS to the string
     31    consisting of <space>, <tab>, and <newline>.  After this (*not*
     32    before!), the PATH variable should be set to a reasonable value and
     33    exported.  Do not expect the PATH to have a reasonable value, so do
     34    not trust the old value of PATH.  You should then set the umask of
     35    the program by calling
     36     umask 077 # or 022 if you want the files to be readable
     37    If you plan to change directories, you should either unset CDPATH
     38    or set it to a good value.  Setting CDPATH to just ``.'' (dot) is a
     39    good idea.
     40    If, for some reason, you want to use csh, the first line should be
     41     #!/bin/csh -fb
     42    You should then set the path variable to something reasonable,
     43    without trusting the inherited path.  Here too, you should set the
     44    umask using the command
     45     umask 077 # or 022 if you want the files to be readable
     46 */
     47 
     48 #include <unistd.h>
     49 #include <stdlib.h>
     50 #include <stdio.h>
     51 #include <sys/types.h>
     52 #include <sys/stat.h>
     53 #include <string.h>
     54 
     55 /* CONFIGURATION SECTION */
     56 
     57 #ifndef FULL_PATH       /* so that this can be specified from the Makefile */
     58 /* Uncomment the following line:
     59 #define FULL_PATH       "/full/path/of/script"
     60 * Then comment out the #error line. */
     61 #error "You must define FULL_PATH somewhere"
     62 #endif
     63 #ifndef UMASK
     64 #define UMASK           077
     65 #endif
     66 
     67 /* END OF CONFIGURATION SECTION */
     68 
     69 #if defined(__STDC__) && defined(__sgi)
     70 #define environ _environ
     71 #endif
     72 
     73 /* don't change def_IFS */
     74 char def_IFS[] = "IFS= \t\n";
     75 /* you may want to change def_PATH, but you should really change it in */
     76 /* your script */
     77 #ifdef __sgi
     78 char def_PATH[] = "PATH=/usr/bsd:/usr/bin:/bin:/usr/local/bin:/usr/sbin";
     79 #else
     80 char def_PATH[] = "PATH=/usr/ucb:/usr/bin:/bin:/usr/local/bin";
     81 #endif
     82 /* don't change def_CDPATH */
     83 char def_CDPATH[] = "CDPATH=.";
     84 /* don't change def_ENV */
     85 char def_ENV[] = "ENV=:";
     86 
     87 /*
     88    This function changes all environment variables that start with LD_
     89    into variables that start with XD_.  This is important since we
     90    don't want the script that is executed to use any funny shared
     91    libraries.
     92 
     93    The other changes to the environment are, strictly speaking, not
     94    needed here.  They can safely be done in the script.  They are done
     95    here because we don't trust the script writer (just like the script
     96    writer shouldn't trust the user of the script).
     97    If IFS is set in the environment, set it to space,tab,newline.
     98    If CDPATH is set in the environment, set it to ``.''.
     99    Set PATH to a reasonable default.
    100 */
    101 void
    102 clean_environ(void)
    103 {
    104     char **p;
    105     extern char **environ;
    106 
    107     for (p = environ; *p; p++) {
    108         if (strncmp(*p, "LD_", 3) == 0)
    109             **p = 'X';
    110         else if (strncmp(*p, "_RLD", 4) == 0)
    111             **p = 'X';
    112         else if (strncmp(*p, "PYTHON", 6) == 0)
    113             **p = 'X';
    114         else if (strncmp(*p, "IFS=", 4) == 0)
    115             *p = def_IFS;
    116         else if (strncmp(*p, "CDPATH=", 7) == 0)
    117             *p = def_CDPATH;
    118         else if (strncmp(*p, "ENV=", 4) == 0)
    119             *p = def_ENV;
    120     }
    121     putenv(def_PATH);
    122 }
    123 
    124 int
    125 main(int argc, char **argv)
    126 {
    127     struct stat statb;
    128     gid_t egid = getegid();
    129     uid_t euid = geteuid();
    130 
    131     /*
    132        Sanity check #1.
    133        This check should be made compile-time, but that's not possible.
    134        If you're sure that you specified a full path name for FULL_PATH,
    135        you can omit this check.
    136     */
    137     if (FULL_PATH[0] != '/') {
    138         fprintf(stderr, "%s: %s is not a full path name\n", argv[0],
    139             FULL_PATH);
    140         fprintf(stderr, "You can only use this wrapper if you\n");
    141         fprintf(stderr, "compile it with an absolute path.\n");
    142         exit(1);
    143     }
    144 
    145     /*
    146        Sanity check #2.
    147        Check that the owner of the script is equal to either the
    148        effective uid or the super user.
    149     */
    150     if (stat(FULL_PATH, &statb) < 0) {
    151         perror("stat");
    152         exit(1);
    153     }
    154     if (statb.st_uid != 0 && statb.st_uid != euid) {
    155         fprintf(stderr, "%s: %s has the wrong owner\n", argv[0],
    156             FULL_PATH);
    157         fprintf(stderr, "The script should be owned by root,\n");
    158         fprintf(stderr, "and shouldn't be writeable by anyone.\n");
    159         exit(1);
    160     }
    161 
    162     if (setregid(egid, egid) < 0)
    163         perror("setregid");
    164     if (setreuid(euid, euid) < 0)
    165         perror("setreuid");
    166 
    167     clean_environ();
    168 
    169     umask(UMASK);
    170 
    171     while (**argv == '-')       /* don't let argv[0] start with '-' */
    172         (*argv)++;
    173     execv(FULL_PATH, argv);
    174     fprintf(stderr, "%s: could not execute the script\n", argv[0]);
    175     exit(1);
    176 }
    177