OSDN Git Service

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