blob: 496d51f136bf56a6eb5cdbd0ed1f7802c2a07397 [file] [log] [blame]
Zach Rigglebaf83612016-07-21 11:21:54 -04001/*
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 */
16const char* optstr = "<1u:g:G:c:s";
17const char* usage =
18 R"(usage: runconuid [-s] [-u UID] [-g GID] [-G GROUPS] [-c CONTEXT] COMMAND ARGS
19
20Run a command in the specified security context, as the specified user,
21with 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
42static uid_t uid = -1;
43static gid_t gid = -1;
44static gid_t* groups = nullptr;
45static size_t ngroups = 0;
46static char* context = nullptr;
47static bool setenforce = false;
48static char** child_argv = nullptr;
49
50[[noreturn]] void perror_exit(const char* message) {
51 perror(message);
52 exit(1);
53}
54
55void 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
78uid_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
93gid_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
108void 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
133void 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
166int 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}