blob: 230c58dfb2a68ea5651107ef8c87d5640ed5db83 [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>
Dan Albert7860f052017-10-11 11:41:57 -070031#include <errno.h>
Zach Rigglebaf83612016-07-21 11:21:54 -040032#include <grp.h>
33#include <pwd.h>
34#include <selinux/selinux.h>
Dan Albert7860f052017-10-11 11:41:57 -070035#include <signal.h>
Zach Rigglebaf83612016-07-21 11:21:54 -040036#include <stdio.h>
37#include <stdlib.h>
Dan Albert7860f052017-10-11 11:41:57 -070038#include <string.h>
Zach Rigglebaf83612016-07-21 11:21:54 -040039#include <sys/ptrace.h>
40#include <sys/types.h>
41#include <sys/wait.h>
42#include <unistd.h>
43
44static uid_t uid = -1;
45static gid_t gid = -1;
46static gid_t* groups = nullptr;
47static size_t ngroups = 0;
48static char* context = nullptr;
49static bool setenforce = false;
50static char** child_argv = nullptr;
51
52[[noreturn]] void perror_exit(const char* message) {
53 perror(message);
54 exit(1);
55}
56
57void 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
80uid_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
95gid_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
110void 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
135void 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
168int 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}