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