242 lines
5.2 KiB
C++
242 lines
5.2 KiB
C++
|
/*
|
||
|
* 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;
|
||
|
}
|