2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 const char* optstr = "<1u:g:G:c:s";
18 R"(usage: runconuid [-s] [-u UID] [-g GID] [-G GROUPS] [-c CONTEXT] COMMAND ARGS
20 Run a command in the specified security context, as the specified user,
21 with the specified group membership.
24 -g Group ID by name or numeric value
25 -G List of groups by name or numeric value
27 -u User ID by name or numeric value
33 #include <selinux/selinux.h>
37 #include <sys/ptrace.h>
38 #include <sys/types.h>
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;
50 [[noreturn]] void perror_exit(const char* message) {
56 if (context && setexeccon(context) < 0) {
57 perror_exit("Setting context to failed");
60 if (ngroups && setgroups(ngroups, groups) < 0) {
61 perror_exit("Setting supplementary groups failed.");
64 if (gid != (gid_t) -1 && setresgid(gid, gid, gid) < 0) {
65 perror_exit("Setting group failed.");
68 if (uid != (uid_t) -1 && setresuid(uid, uid, uid) < 0) {
69 perror_exit("Setting user failed.");
72 ptrace(PTRACE_TRACEME, 0, 0, 0);
74 execvp(child_argv[0], child_argv);
75 perror_exit("Failed to execve");
78 uid_t lookup_uid(char* c) {
82 if (sscanf(c, "%d", &u) == 1) {
86 if ((pw = getpwnam(c)) != 0) {
90 perror_exit("Could not resolve user ID by name");
93 gid_t lookup_gid(char* c) {
97 if (sscanf(c, "%d", &g) == 1) {
101 if ((gr = getgrnam(c)) != 0) {
105 perror_exit("Could not resolve group ID by name");
108 void lookup_groups(char* c) {
111 // Count the number of groups
112 for (group = c; *group; group++) {
119 // The last group is not followed by a comma.
122 // Allocate enough space for all of them
123 groups = (gid_t*)calloc(ngroups, sizeof(gid_t));
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;
133 void parse_arguments(int argc, char** argv) {
136 while ((c = getopt(argc, argv, optstr)) != -1) {
139 uid = lookup_uid(optarg);
142 gid = lookup_gid(optarg);
145 lookup_groups(optarg);
159 child_argv = &argv[optind];
161 if (optind == argc) {
166 int main(int argc, char** argv) {
169 parse_arguments(argc, argv);
173 perror_exit("Could not fork.");
176 if (setenforce && is_selinux_enabled()) {
177 if (security_setenforce(0) < 0) {
178 perror("Couldn't set enforcing status to 0");
186 if (ptrace(PTRACE_ATTACH, child, 0, 0) < 0) {
188 kill(SIGKILL, child);
190 perror_exit("Could not ptrace child.");
193 // Wait for the SIGSTOP
195 if (-1 == wait(&status)) {
196 perror_exit("Could not wait for child SIGSTOP");
199 // Trace all syscalls.
200 ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
203 ptrace(PTRACE_SYSCALL, child, 0, 0);
204 waitpid(child, &status, 0);
206 // Child raises SIGINT after the execve, on the first instruction.
207 if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
211 // Child did some other syscall.
212 if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) {
217 if (WIFEXITED(status)) {
218 exit(WEXITSTATUS(status));
222 if (setenforce && is_selinux_enabled()) {
223 if (security_setenforce(1) < 0) {
224 perror("Couldn't set enforcing status to 1");
228 ptrace(PTRACE_DETACH, child, 0, 0);