/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const char* optstr = "<1u:g:G:c:s"; const char* usage = R"(usage: runconuid [-s] [-u UID] [-g GID] [-G GROUPS] [-c CONTEXT] COMMAND ARGS Run a command in the specified security context, as the specified user, with the specified group membership. -c SELinux context -g Group ID by name or numeric value -G List of groups by name or numeric value -s Set enforcing mode -u User ID by name or numeric value )"; #include <assert.h> #include <errno.h> #include <grp.h> #include <pwd.h> #include <selinux/selinux.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/capability.h> #include <sys/prctl.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> static uid_t uid = -1; static gid_t gid = -1; static gid_t* groups = nullptr; static size_t ngroups = 0; static char* context = nullptr; static bool setenforce = false; static char** child_argv = nullptr; [[noreturn]] void perror_exit(const char* message) { perror(message); exit(1); } void do_child(void) { if (context && setexeccon(context) < 0) { perror_exit("Setting context to failed"); } // Disregard ambient capability failures, we may just be on a kernel // that does not support them. for (int i = 0; i < 64; ++i) { prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0); } if (ngroups && setgroups(ngroups, groups) < 0) { perror_exit("Setting supplementary groups failed."); } if (gid != (gid_t) -1 && setresgid(gid, gid, gid) < 0) { perror_exit("Setting group failed."); } if (uid != (uid_t) -1 && setresuid(uid, uid, uid) < 0) { perror_exit("Setting user failed."); } ptrace(PTRACE_TRACEME, 0, 0, 0); raise(SIGSTOP); execvp(child_argv[0], child_argv); perror_exit("Failed to execve"); } uid_t lookup_uid(char* c) { struct passwd* pw; uid_t u; if (sscanf(c, "%d", &u) == 1) { return u; } if ((pw = getpwnam(c)) != 0) { return pw->pw_uid; } perror_exit("Could not resolve user ID by name"); } gid_t lookup_gid(char* c) { struct group* gr; gid_t g; if (sscanf(c, "%d", &g) == 1) { return g; } if ((gr = getgrnam(c)) != 0) { return gr->gr_gid; } perror_exit("Could not resolve group ID by name"); } void lookup_groups(char* c) { char* group; // Count the number of groups for (group = c; *group; group++) { if (*group == ',') { ngroups++; *group = '\0'; } } // The last group is not followed by a comma. ngroups++; // Allocate enough space for all of them groups = (gid_t*)calloc(ngroups, sizeof(gid_t)); group = c; // Fill in the group IDs for (size_t n = 0; n < ngroups; n++) { groups[n] = lookup_gid(group); group += strlen(group) + 1; } } void parse_arguments(int argc, char** argv) { int c; while ((c = getopt(argc, argv, optstr)) != -1) { switch (c) { case 'u': uid = lookup_uid(optarg); break; case 'g': gid = lookup_gid(optarg); break; case 'G': lookup_groups(optarg); break; case 's': setenforce = true; break; case 'c': context = optarg; break; default: perror_exit(usage); break; } } child_argv = &argv[optind]; if (optind == argc) { perror_exit(usage); } } int main(int argc, char** argv) { pid_t child; parse_arguments(argc, argv); child = fork(); if (child < 0) { perror_exit("Could not fork."); } if (setenforce && is_selinux_enabled()) { if (security_setenforce(0) < 0) { perror("Couldn't set enforcing status to 0"); } } if (child == 0) { do_child(); } if (ptrace(PTRACE_ATTACH, child, 0, 0) < 0) { int err = errno; kill(SIGKILL, child); errno = err; perror_exit("Could not ptrace child."); } // Wait for the SIGSTOP int status = 0; if (-1 == wait(&status)) { perror_exit("Could not wait for child SIGSTOP"); } // Trace all syscalls. ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD); while (1) { ptrace(PTRACE_SYSCALL, child, 0, 0); waitpid(child, &status, 0); // Child raises SIGINT after the execve, on the first instruction. if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { break; } // Child did some other syscall. if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) { continue; } // Child exited. if (WIFEXITED(status)) { exit(WEXITSTATUS(status)); } } if (setenforce && is_selinux_enabled()) { if (security_setenforce(1) < 0) { perror("Couldn't set enforcing status to 1"); } } ptrace(PTRACE_DETACH, child, 0, 0); return 0; }