You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
4.9 KiB
230 lines
4.9 KiB
/* |
|
* 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 <grp.h> |
|
#include <pwd.h> |
|
#include <selinux/selinux.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <signal.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"); |
|
} |
|
|
|
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; |
|
}
|
|
|