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