"Fossies" - the Fresh Open Source Software Archive

Member "jailkit-2.21/src/jk_uchroot.c" (11 Mar 2016, 14210 Bytes) of package /linux/privat/jailkit-2.21.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "jk_uchroot.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2.19_vs_2.20.

    1 /*
    2  * the jailkit chroot() shell
    3  * this program does a safe chroot() and then executes the shell
    4  * that the user has within that new root (according to newroot/etc/passwd)
    5  *
    6  * I tried to merge some of the ideas from chrsh by Aaron D. Gifford,
    7  * start-stop-daemon from Marek Michalkiewicz and suexec by the Apache
    8  * group in this shell
    9  *
   10 
   11 Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2015, 2016 Olivier Sessink
   12 All rights reserved.
   13 
   14 Redistribution and use in source and binary forms, with or without
   15 modification, are permitted provided that the following conditions
   16 are met:
   17   * Redistributions of source code must retain the above copyright
   18     notice, this list of conditions and the following disclaimer.
   19   * Redistributions in binary form must reproduce the above
   20     copyright notice, this list of conditions and the following
   21     disclaimer in the documentation and/or other materials provided
   22     with the distribution.
   23   * The names of its contributors may not be used to endorse or
   24     promote products derived from this software without specific
   25     prior written permission.
   26 
   27 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   29 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   30 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   31 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   32 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   33 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   34 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   35 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   36 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   37 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   38 POSSIBILITY OF SUCH DAMAGE.
   39  */
   40 
   41 #include "config.h"
   42 
   43 #include <stdio.h>
   44 #include <stdlib.h>
   45 #include <unistd.h>
   46 #include <string.h>
   47 #include <pwd.h>
   48 #include <grp.h>
   49 #include <sys/stat.h>
   50 #include <sys/types.h>
   51 #include <errno.h>
   52 #include <syslog.h>
   53 #include <limits.h>
   54 #include <fcntl.h>
   55 #ifdef HAVE_GETOPT_H
   56 #include <getopt.h>
   57 #endif
   58 #ifdef HAVE_LIBERTY_H
   59 #include <liberty.h>
   60 #endif
   61 #ifdef HAVE_SYS_CAPABILITY_H
   62 #include <sys/capability.h>
   63 #endif
   64 /*#define DEBUG*/
   65 
   66 #ifdef DEBUG
   67 #define DEBUG_MSG printf
   68 #else
   69 #define DEBUG_MSG(args...)
   70  /**/
   71 #endif
   72 
   73 #define PROGRAMNAME "jk_uchroot"
   74 #define CONFIGFILE INIPREFIX"/jk_uchroot.ini"
   75 
   76 #include "jk_lib.h"
   77 #include "utils.h"
   78 #include "iniparser.h"
   79 #include "passwdparser.h"
   80 
   81 /* doesn't compile on FreeBSD without this */
   82 extern char **environ;
   83 
   84 static void print_usage() {
   85     printf(PACKAGE" "VERSION"\nUsage: "PROGRAMNAME" -j jaildir -x executable -- [executable options]\n");
   86     printf("\t-j|--jail jaildir\n");
   87     printf("\t-x|--exec executable\n");
   88     printf("\t-h|--help\n");
   89     printf(PROGRAMNAME" logs all errors to syslog, for diagnostics check your logfiles\n");
   90 }
   91 
   92 static int have_capabilities(void) {
   93 #ifdef HAVE_CAP_GET_PROC
   94     cap_t caps = cap_get_proc();
   95     if (caps) {
   96         cap_flag_value_t value_p;
   97         cap_get_flag(caps, CAP_SYS_CHROOT, CAP_EFFECTIVE,&value_p);
   98         cap_free(caps);
   99         return (value_p);
  100     }
  101 #endif  /*HAVE_CAP_GET_PROC*/
  102     return 0;
  103 }
  104 
  105 /* check basics */
  106 /* parse arguments */
  107 /* parse configfile */
  108 /* check user info */
  109 /* check jail */
  110 /* do chroot call */
  111 int main(int argc, char **argv) {
  112     char *jail = NULL;
  113     char *user = NULL;
  114     char *executable = NULL;
  115     struct passwd *pw=NULL;
  116     struct group *gr=NULL;
  117     Tiniparser *parser=NULL;
  118     char **newargv=NULL;
  119     char **allowed_jails = NULL;
  120     unsigned int skip_injail_passwd_check=0;
  121     unsigned int i;
  122     char *tmp;
  123     unsigned int use_capabilities=0;
  124 
  125     openlog(PROGRAMNAME, LOG_PID, LOG_AUTH);
  126 
  127     /* check if it us that the user wants */
  128     {
  129         char *tmp = strrchr(argv[0], '/');
  130         if (!tmp) {
  131             tmp = argv[0];
  132         } else {
  133             tmp++;
  134         }
  135         if (strcmp(tmp, PROGRAMNAME) && (tmp[0] != '-' || strcmp(&tmp[1], PROGRAMNAME))) {
  136             DEBUG_MSG("wrong name, tmp=%s, &tmp[1]=%s\n", tmp, &tmp[1]);
  137             syslog(LOG_ERR, "abort, "PROGRAMNAME" is called as %s", argv[0]);
  138             exit(1);
  139         }
  140     }
  141 
  142     /* now test if we are setuid root (the effective user id must be 0, and the real user id > 0 */
  143 #ifndef DEVELOPMENT
  144     if (geteuid() != 0) {
  145         if (have_capabilities()) {
  146             use_capabilities=1;
  147         } else {
  148             syslog(LOG_ERR, "abort, effective user ID is not 0, possibly "PROGRAMNAME" is not setuid root");
  149             exit(11);
  150         }
  151     }
  152 #endif
  153     if (getuid() == 0) {
  154         syslog(LOG_ERR, "abort, "PROGRAMNAME" is run by root, which does not make sense because user root can use the chroot utility");
  155         exit(12);
  156     }
  157 
  158 
  159 
  160     DEBUG_MSG("get user info\n");
  161     /* get user info based on the users name and not on the uid. this enables support
  162     for systems with multiple users with the same user id*/
  163     tmp = getenv("USER");
  164     if (tmp && strlen(tmp)) {
  165         user = strdup(tmp);
  166     }
  167     if (user) {
  168         pw = getpwnam(user);
  169     } else {
  170         pw = getpwuid(getuid());
  171     }
  172 
  173     if (!pw) {
  174         syslog(LOG_ERR, "abort, failed to get user information for user ID %u: %s, check /etc/passwd", getuid(), strerror(errno));
  175         exit(13);
  176     }
  177     if (!pw->pw_name || strlen(pw->pw_name)==0) {
  178         syslog(LOG_ERR, "abort, got an empty username for user ID %u: %s, check /etc/passwd", getuid(), strerror(errno));
  179         exit(13);
  180     }
  181     if (user && strcmp(user,pw->pw_name)!=0) {
  182         syslog(LOG_ERR, "abort, asked for user %s, got user info for %s", user, pw->pw_name);
  183         exit(13);
  184     }
  185     if (pw->pw_uid != getuid()) {
  186         syslog(LOG_ERR, "abort, started by user ID %u, got user info %s with user ID %u,", getuid(), pw->pw_name, pw->pw_uid);
  187         exit(13);
  188     }
  189     gr = getgrgid(getgid());
  190     if (!gr) {
  191         syslog(LOG_ERR, "abort, failed to get group information for group ID %u: %s, check /etc/group", getgid(), strerror(errno));
  192         exit(13);
  193     }
  194 
  195 
  196     {
  197         int c=0;
  198         int len;
  199         char *execplusjailpath;
  200         while (c != -1) {
  201             int option_index = 0;
  202             static struct option long_options[] = {
  203                 {"jail", required_argument, NULL, 'j'},
  204                 {"executable", required_argument, NULL, 'x'},
  205                 {"help", no_argument, NULL, 'h'},
  206                 {"version", no_argument, NULL, 'V'},
  207                 {NULL, 0, NULL, 0}
  208             };
  209             c = getopt_long(argc, argv, "j:x:hv",long_options, &option_index);
  210             switch (c) {
  211             case 'j':
  212                 jail = ending_slash(optarg);
  213                 DEBUG_MSG("argument jail='%s', ending_slash returned '%s'\n",optarg,jail);
  214                 break;
  215             case 'x':
  216                 executable = strdup(optarg);
  217                 break;
  218             case 'h':
  219             case 'V':
  220                 print_usage();
  221                 exit(1);
  222             }
  223         }
  224         /* construct the new argv from all leftover options */
  225         newargv = malloc0((2 + argc - optind)*sizeof(char *));
  226         newargv[0] = executable;
  227         c = 1;
  228         while (optind < argc) {
  229             newargv[c] = strdup(argv[optind]);
  230             c++;
  231             optind++;
  232         }
  233 
  234         if (jail == NULL) {
  235             printf("ERROR: No jail path specified. Use -j or --jail\n");
  236             print_usage();
  237             exit(1);
  238         }
  239         if (executable == NULL) {
  240             printf("ERROR: No executable path specified. Use -x or --executable\n");
  241             print_usage();
  242             exit(1);
  243         }
  244         len = strlen(jail)+strlen(executable)+2;
  245         execplusjailpath = malloc(len+1);
  246         snprintf(execplusjailpath, len, "%s/%s",jail, executable);
  247         if (!file_exists(execplusjailpath)) {
  248             printf("ERROR: Executable path %s does not exist or is not a regular file\n", execplusjailpath);
  249             print_usage();
  250             exit(1);
  251         }
  252         free(execplusjailpath);
  253     }
  254     /* make sure the jailkit config directory is owned root:root and not writable for others */
  255     if ( (testsafepath(INIPREFIX, 0, 0) &~TESTPATH_GROUPW) != 0 ) {
  256         syslog(LOG_ERR, "abort, jailkit configuration directory "INIPREFIX" is not safe; it should be owned 0:0 and not writable for others");
  257         exit(14);
  258     }
  259     parser = new_iniparser(CONFIGFILE);
  260     if (parser) {
  261         /* first check for a section specific for this user, then for a group section, else a DEFAULT section */
  262         char *groupsec, *section=NULL, buffer[1024]; /* openbsd complains if this is <1024 */
  263         groupsec = strcat(strcpy(malloc0(strlen(gr->gr_name)+7), "group "), gr->gr_name);
  264         if (iniparser_has_section(parser, pw->pw_name)) {
  265             section = strdup(pw->pw_name);
  266         } else if (iniparser_has_section(parser, groupsec)) {
  267             section = groupsec;
  268         } else if (iniparser_has_section(parser, "DEFAULT")) {
  269             section = strdup("DEFAULT");
  270         }
  271         if (section != groupsec) free(groupsec);
  272         if (section) {
  273             /* from this section, retrieve the options
  274              - which jails are allowed
  275              - which shell to use
  276              - if the user has to be in <jail>/etc/passwd (only if shell is given)
  277              */
  278             unsigned int pos = iniparser_get_position(parser) - strlen(section) - 2;
  279             if (iniparser_get_string_at_position(parser, section, "allowed_jails", pos, buffer, 1024) > 0) {
  280                 DEBUG_MSG("found allowed_jails=%s\n",buffer);
  281                 allowed_jails = explode_string(buffer, ',');
  282             }
  283 
  284             skip_injail_passwd_check = iniparser_get_int_at_position(parser, section, "skip_injail_passwd_check", pos, 0);
  285 
  286             free(section);
  287         } else {
  288             DEBUG_MSG("no relevant section found in configfile\n");
  289         }
  290         if (allowed_jails == NULL) {
  291             syslog(LOG_ERR,"abort, no relevant section for user %s (%u) or group %s (%u) or DEFAULT found in "CONFIGFILE, pw->pw_name,getuid(),gr->gr_name,getgid());
  292             exit(1);
  293         }
  294         iniparser_close(parser);
  295     } else {
  296         DEBUG_MSG("no configfile "CONFIGFILE" ??\n");
  297         syslog(LOG_ERR,"abort, no config file "CONFIGFILE);
  298         exit(1);
  299     }
  300 
  301 #ifdef OPEN_MAX
  302     i = OPEN_MAX;
  303 #elif defined(NOFILE)
  304     i = NOFILE;
  305 #else
  306     i = getdtablesize();
  307 #endif
  308     while (--i > 2) {
  309         while (close(i) != 0 && errno == EINTR);
  310     }
  311     /* now make sure file descriptors 0 1 and 2 are valid before we (or a child) starts writing to it */
  312     while (1) {
  313         int fd;
  314         fd = open("/dev/null", O_RDWR);
  315         if (fd < 0)
  316             exit(10);
  317         if (fd > 2) {
  318             close(fd);
  319             break;
  320         }
  321     }
  322 
  323 
  324     /* check if the requested jail is allowed */
  325     {
  326         unsigned int allowed = 0;
  327         /* 'jail' has an ending slash */
  328         for (i=0;allowed_jails[i]!=NULL&&!allowed;i++) {
  329             allowed = dirs_equal(jail,allowed_jails[i]);
  330             DEBUG_MSG("allowed=%d after testing '%s' with '%s'\n",allowed,jail,allowed_jails[i]);
  331         }
  332         if (allowed!=1) {
  333             syslog(LOG_ERR,"abort, user %s (%u) is not allowed in jail %s",pw->pw_name, getuid(),jail);
  334             exit(21);
  335         }
  336     }
  337     /* test the jail */
  338     if (!basicjailissafe(jail)) {
  339         syslog(LOG_ERR, "abort, jail %s is not safe, check ownership and permissions for the jail inclusing system directories such as /etc, /lib, /usr, /dev, /sbin, and /bin", jail);
  340         exit(53);
  341     }
  342 
  343     if (chdir(jail) != 0) {
  344         syslog(LOG_ERR, "abort, chdir(%s) failed: %s",jail,strerror(errno));
  345         exit(19);
  346     } else {
  347         char test[1024];
  348         /* test if it really succeeded */
  349         if (getcwd(test, 1024)==NULL || !dirs_equal(jail, test)) {
  350             syslog(LOG_ERR, "abort, the current dir is %s after chdir(%s), but it should be %s",test,jail,jail);
  351             exit(21);
  352         }
  353     }
  354 
  355     syslog(LOG_INFO, "entering jail %s for user %s (%u) in order to execute %s", jail, pw->pw_name, getuid(), executable);
  356 
  357     /* do the chroot() call */
  358     if (chroot(jail)) {
  359         syslog(LOG_ERR, "abort, chroot(%s) failed: %s", jail, strerror(errno));
  360         exit(33);
  361     }
  362 
  363     if (use_capabilities) {
  364 #ifdef HAVE_CAP_GET_PROC
  365         cap_t caps;
  366         cap_value_t capv[1];
  367         /* drop chroot capability, should we drop all other capabilities that may be used to escape from the jail too ?  */
  368         if ((caps = cap_get_proc()) == NULL) {
  369             syslog(LOG_ERR, "abort, failed to retrieve current capabilities: %s", strerror(errno));
  370             exit(101);
  371         }
  372         capv[0] = CAP_SYS_CHROOT;
  373         /* other capabilities that should/could be dropped:
  374         CAP_SETPCAP, CAP_SYS_MODULE, CAP_SYS_RAWIO, CAP_SYS_PTRACE, CAP_SYS_ADMIN */
  375         if (cap_set_flag(caps, CAP_PERMITTED, 1, capv, CAP_CLEAR)) {
  376             syslog(LOG_ERR, "abort, failed to set PERMITTED capabilities: %s", strerror(errno));
  377             exit(102);
  378         }
  379         if (cap_set_flag(caps, CAP_EFFECTIVE, 1, capv, CAP_CLEAR)) {
  380             syslog(LOG_ERR, "abort, failed to set effective capabilities: %s", strerror(errno));
  381             exit(103);
  382         }
  383         if (cap_set_flag(caps, CAP_INHERITABLE, 1, capv, CAP_CLEAR)) {
  384             syslog(LOG_ERR, "abort, failed to set INHERITABLE capabilities: %s", strerror(errno));
  385             exit(104);
  386         }
  387         if (cap_set_proc(caps)) {
  388             syslog(LOG_ERR, "abort, failed to apply new capabilities: %s", strerror(errno));
  389             exit(105);
  390         }
  391 #else
  392         /* we should never get here */
  393         exit(333);
  394 #endif
  395     } else {
  396         /* drop all privileges, we first have to setgid(),
  397             then we call setuid() */
  398         if (setgid(getgid())) {
  399             syslog(LOG_ERR, "abort, failed to set effective group ID %u: %s", getgid(), strerror(errno));
  400             exit(34);
  401         }
  402         if (setuid(getuid())) {
  403             syslog(LOG_ERR, "abort, failed to set effective user ID %u: %s", getuid(), strerror(errno));
  404             exit(36);
  405         }
  406     }
  407 
  408     if (!skip_injail_passwd_check){
  409         char *oldpw_name,*oldgr_name;
  410         oldpw_name = strdup(pw->pw_name);
  411         oldgr_name = strdup(gr->gr_name);
  412 
  413         if (user) {
  414             pw = getpwnam(user);
  415         } else {
  416             pw = getpwuid(getuid());
  417         }
  418         if (!pw) {
  419             syslog(LOG_ERR, "abort, failed to get user information in the jail for user ID %u: %s, check %s/etc/passwd",getuid(),strerror(errno),jail);
  420             exit(35);
  421         }
  422         DEBUG_MSG("got %s as pw_dir\n",pw->pw_dir);
  423         if (pw->pw_uid != getuid()) {
  424             syslog(LOG_ERR, "abort, got user information in the jail for user ID %u instead of user ID %u, check %s/etc/passwd",pw->pw_uid,getuid(),jail);
  425             exit(35);
  426         }
  427         gr = getgrgid(getgid());
  428         if (!gr) {
  429             syslog(LOG_ERR, "abort, failed to get group information in the jail for group ID %u: %s, check %s/etc/group",getgid(),strerror(errno),jail);
  430             exit(35);
  431         }
  432         if (strcmp(pw->pw_name, oldpw_name)!=0) {
  433             syslog(LOG_ERR, "abort, username %s differs from jail username %s for user ID %u, check /etc/passwd and %s/etc/passwd", oldpw_name, pw->pw_name, getuid(), jail);
  434             exit(37);
  435         }
  436         if (strcmp(gr->gr_name, oldgr_name)!=0) {
  437             syslog(LOG_ERR, "abort, groupname %s differs from jail groupname %s for group ID %u, check /etc/passwd and %s/etc/passwd", oldgr_name, gr->gr_name, getgid(), jail);
  438             exit(37);
  439         }
  440         free(oldpw_name);
  441         free(oldgr_name);
  442     }
  443 
  444     /* now execute the jailed shell */
  445     execv(executable, newargv);
  446     /* normally we wouldn't come to this bit of code */
  447     syslog(LOG_ERR, "ERROR: failed to execute %s for user %s (%u), check the permissions and libraries of %s%s",executable,pw->pw_name,getuid(),jail,executable);
  448 
  449     free(jail);
  450     exit(111);
  451 }