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 <errno.h> 32 #include <grp.h> 33 #include <pwd.h> 34 #include <selinux/selinux.h> 35 #include <signal.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <sys/capability.h> 40 #include <sys/prctl.h> 41 #include <sys/ptrace.h> 42 #include <sys/types.h> 43 #include <sys/wait.h> 44 #include <unistd.h> 45 46 static uid_t uid = -1; 47 static gid_t gid = -1; 48 static gid_t* groups = nullptr; 49 static size_t ngroups = 0; 50 static char* context = nullptr; 51 static bool setenforce = false; 52 static char** child_argv = nullptr; 53 54 [[noreturn]] void perror_exit(const char* message) { 55 perror(message); 56 exit(1); 57 } 58 59 void do_child(void) { 60 61 if (context && setexeccon(context) < 0) { 62 perror_exit("Setting context to failed"); 63 } 64 65 // Disregard ambient capability failures, we may just be on a kernel 66 // that does not support them. 67 for (int i = 0; i < 64; ++i) { 68 prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0); 69 } 70 71 if (ngroups && setgroups(ngroups, groups) < 0) { 72 perror_exit("Setting supplementary groups failed."); 73 } 74 75 if (gid != (gid_t) -1 && setresgid(gid, gid, gid) < 0) { 76 perror_exit("Setting group failed."); 77 } 78 79 if (uid != (uid_t) -1 && setresuid(uid, uid, uid) < 0) { 80 perror_exit("Setting user failed."); 81 } 82 83 ptrace(PTRACE_TRACEME, 0, 0, 0); 84 raise(SIGSTOP); 85 execvp(child_argv[0], child_argv); 86 perror_exit("Failed to execve"); 87 } 88 89 uid_t lookup_uid(char* c) { 90 struct passwd* pw; 91 uid_t u; 92 93 if (sscanf(c, "%d", &u) == 1) { 94 return u; 95 } 96 97 if ((pw = getpwnam(c)) != 0) { 98 return pw->pw_uid; 99 } 100 101 perror_exit("Could not resolve user ID by name"); 102 } 103 104 gid_t lookup_gid(char* c) { 105 struct group* gr; 106 gid_t g; 107 108 if (sscanf(c, "%d", &g) == 1) { 109 return g; 110 } 111 112 if ((gr = getgrnam(c)) != 0) { 113 return gr->gr_gid; 114 } 115 116 perror_exit("Could not resolve group ID by name"); 117 } 118 119 void lookup_groups(char* c) { 120 char* group; 121 122 // Count the number of groups 123 for (group = c; *group; group++) { 124 if (*group == ',') { 125 ngroups++; 126 *group = '\0'; 127 } 128 } 129 130 // The last group is not followed by a comma. 131 ngroups++; 132 133 // Allocate enough space for all of them 134 groups = (gid_t*)calloc(ngroups, sizeof(gid_t)); 135 group = c; 136 137 // Fill in the group IDs 138 for (size_t n = 0; n < ngroups; n++) { 139 groups[n] = lookup_gid(group); 140 group += strlen(group) + 1; 141 } 142 } 143 144 void parse_arguments(int argc, char** argv) { 145 int c; 146 147 while ((c = getopt(argc, argv, optstr)) != -1) { 148 switch (c) { 149 case 'u': 150 uid = lookup_uid(optarg); 151 break; 152 case 'g': 153 gid = lookup_gid(optarg); 154 break; 155 case 'G': 156 lookup_groups(optarg); 157 break; 158 case 's': 159 setenforce = true; 160 break; 161 case 'c': 162 context = optarg; 163 break; 164 default: 165 perror_exit(usage); 166 break; 167 } 168 } 169 170 child_argv = &argv[optind]; 171 172 if (optind == argc) { 173 perror_exit(usage); 174 } 175 } 176 177 int main(int argc, char** argv) { 178 pid_t child; 179 180 parse_arguments(argc, argv); 181 child = fork(); 182 183 if (child < 0) { 184 perror_exit("Could not fork."); 185 } 186 187 if (setenforce && is_selinux_enabled()) { 188 if (security_setenforce(0) < 0) { 189 perror("Couldn't set enforcing status to 0"); 190 } 191 } 192 193 if (child == 0) { 194 do_child(); 195 } 196 197 if (ptrace(PTRACE_ATTACH, child, 0, 0) < 0) { 198 int err = errno; 199 kill(SIGKILL, child); 200 errno = err; 201 perror_exit("Could not ptrace child."); 202 } 203 204 // Wait for the SIGSTOP 205 int status = 0; 206 if (-1 == wait(&status)) { 207 perror_exit("Could not wait for child SIGSTOP"); 208 } 209 210 // Trace all syscalls. 211 ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD); 212 213 while (1) { 214 ptrace(PTRACE_SYSCALL, child, 0, 0); 215 waitpid(child, &status, 0); 216 217 // Child raises SIGINT after the execve, on the first instruction. 218 if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { 219 break; 220 } 221 222 // Child did some other syscall. 223 if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) { 224 continue; 225 } 226 227 // Child exited. 228 if (WIFEXITED(status)) { 229 exit(WEXITSTATUS(status)); 230 } 231 } 232 233 if (setenforce && is_selinux_enabled()) { 234 if (security_setenforce(1) < 0) { 235 perror("Couldn't set enforcing status to 1"); 236 } 237 } 238 239 ptrace(PTRACE_DETACH, child, 0, 0); 240 return 0; 241 } 242