• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
perror_exit(const char * message)54 [[noreturn]] void perror_exit(const char* message) {
55   perror(message);
56   exit(1);
57 }
58 
do_child(void)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 
lookup_uid(char * c)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 
lookup_gid(char * c)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 
lookup_groups(char * c)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 
parse_arguments(int argc,char ** argv)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 
main(int argc,char ** argv)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