Home | History | Annotate | Download | only in runconuid
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 const char* optstr = "<1u:g:G:c:s";
     17 const char* usage =
     18     R"(usage: runconuid [-s] [-u UID] [-g GID] [-G GROUPS] [-c CONTEXT] COMMAND ARGS
     19 
     20 Run a command in the specified security context, as the specified user,
     21 with the specified group membership.
     22 
     23 -c  SELinux context
     24 -g  Group ID by name or numeric value
     25 -G  List of groups by name or numeric value
     26 -s  Set enforcing mode
     27 -u  User ID by name or numeric value
     28 )";
     29 
     30 #include <assert.h>
     31 #include <grp.h>
     32 #include <pwd.h>
     33 #include <selinux/selinux.h>
     34 #include <stdio.h>
     35 #include <stdlib.h>
     36 #include <signal.h>
     37 #include <sys/ptrace.h>
     38 #include <sys/types.h>
     39 #include <sys/wait.h>
     40 #include <unistd.h>
     41 
     42 static uid_t uid = -1;
     43 static gid_t gid = -1;
     44 static gid_t* groups = nullptr;
     45 static size_t ngroups = 0;
     46 static char* context = nullptr;
     47 static bool setenforce = false;
     48 static char** child_argv = nullptr;
     49 
     50 [[noreturn]] void perror_exit(const char* message) {
     51   perror(message);
     52   exit(1);
     53 }
     54 
     55 void do_child(void) {
     56   if (context && setexeccon(context) < 0) {
     57     perror_exit("Setting context to failed");
     58   }
     59 
     60   if (ngroups && setgroups(ngroups, groups) < 0) {
     61     perror_exit("Setting supplementary groups failed.");
     62   }
     63 
     64   if (gid != (gid_t) -1 && setresgid(gid, gid, gid) < 0) {
     65     perror_exit("Setting group failed.");
     66   }
     67 
     68   if (uid != (uid_t) -1 && setresuid(uid, uid, uid) < 0) {
     69     perror_exit("Setting user failed.");
     70   }
     71 
     72   ptrace(PTRACE_TRACEME, 0, 0, 0);
     73   raise(SIGSTOP);
     74   execvp(child_argv[0], child_argv);
     75   perror_exit("Failed to execve");
     76 }
     77 
     78 uid_t lookup_uid(char* c) {
     79   struct passwd* pw;
     80   uid_t u;
     81 
     82   if (sscanf(c, "%d", &u) == 1) {
     83     return u;
     84   }
     85 
     86   if ((pw = getpwnam(c)) != 0) {
     87     return pw->pw_uid;
     88   }
     89 
     90   perror_exit("Could not resolve user ID by name");
     91 }
     92 
     93 gid_t lookup_gid(char* c) {
     94   struct group* gr;
     95   gid_t g;
     96 
     97   if (sscanf(c, "%d", &g) == 1) {
     98     return g;
     99   }
    100 
    101   if ((gr = getgrnam(c)) != 0) {
    102     return gr->gr_gid;
    103   }
    104 
    105   perror_exit("Could not resolve group ID by name");
    106 }
    107 
    108 void lookup_groups(char* c) {
    109   char* group;
    110 
    111   // Count the number of groups
    112   for (group = c; *group; group++) {
    113     if (*group == ',') {
    114       ngroups++;
    115       *group = '\0';
    116     }
    117   }
    118 
    119   // The last group is not followed by a comma.
    120   ngroups++;
    121 
    122   // Allocate enough space for all of them
    123   groups = (gid_t*)calloc(ngroups, sizeof(gid_t));
    124   group = c;
    125 
    126   // Fill in the group IDs
    127   for (size_t n = 0; n < ngroups; n++) {
    128     groups[n] = lookup_gid(group);
    129     group += strlen(group) + 1;
    130   }
    131 }
    132 
    133 void parse_arguments(int argc, char** argv) {
    134   int c;
    135 
    136   while ((c = getopt(argc, argv, optstr)) != -1) {
    137     switch (c) {
    138       case 'u':
    139         uid = lookup_uid(optarg);
    140         break;
    141       case 'g':
    142         gid = lookup_gid(optarg);
    143         break;
    144       case 'G':
    145         lookup_groups(optarg);
    146         break;
    147       case 's':
    148         setenforce = true;
    149         break;
    150       case 'c':
    151         context = optarg;
    152         break;
    153       default:
    154         perror_exit(usage);
    155         break;
    156     }
    157   }
    158 
    159   child_argv = &argv[optind];
    160 
    161   if (optind == argc) {
    162     perror_exit(usage);
    163   }
    164 }
    165 
    166 int main(int argc, char** argv) {
    167   pid_t child;
    168 
    169   parse_arguments(argc, argv);
    170   child = fork();
    171 
    172   if (child < 0) {
    173     perror_exit("Could not fork.");
    174   }
    175 
    176   if (setenforce && is_selinux_enabled()) {
    177     if (security_setenforce(0) < 0) {
    178       perror("Couldn't set enforcing status to 0");
    179     }
    180   }
    181 
    182   if (child == 0) {
    183     do_child();
    184   }
    185 
    186   if (ptrace(PTRACE_ATTACH, child, 0, 0) < 0) {
    187     int err = errno;
    188     kill(SIGKILL, child);
    189     errno = err;
    190     perror_exit("Could not ptrace child.");
    191   }
    192 
    193   // Wait for the SIGSTOP
    194   int status = 0;
    195   if (-1 == wait(&status)) {
    196     perror_exit("Could not wait for child SIGSTOP");
    197   }
    198 
    199   // Trace all syscalls.
    200   ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
    201 
    202   while (1) {
    203     ptrace(PTRACE_SYSCALL, child, 0, 0);
    204     waitpid(child, &status, 0);
    205 
    206     // Child raises SIGINT after the execve, on the first instruction.
    207     if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
    208       break;
    209     }
    210 
    211     // Child did some other syscall.
    212     if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) {
    213       continue;
    214     }
    215 
    216     // Child exited.
    217     if (WIFEXITED(status)) {
    218       exit(WEXITSTATUS(status));
    219     }
    220   }
    221 
    222   if (setenforce && is_selinux_enabled()) {
    223     if (security_setenforce(1) < 0) {
    224       perror("Couldn't set enforcing status to 1");
    225     }
    226   }
    227 
    228   ptrace(PTRACE_DETACH, child, 0, 0);
    229   return 0;
    230 }
    231