|  | /* | 
|  | * 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; | 
|  | } |