setpriv: run a program with different Linux privilege settings
This new command can set no_new_privs, uid, gid, groups, securebits, inheritable caps, the cap bounding set, securebits, and selinux and apparmor labels. [kerolasa@iki.fi: a lot of small adjustment making the command to be good fit to util-linux project] Signed-off-by: Sami Kerola <kerolasa@iki.fi> Signed-off-by: Andy Lutomirski <luto@amacapital.net>
This commit is contained in:
parent
cd0fe5c165
commit
5600c405d5
|
@ -150,6 +150,7 @@ tests/run.sh.trs
|
|||
/script
|
||||
/scriptreplay
|
||||
/setarch
|
||||
/setpriv
|
||||
/setsid
|
||||
/setterm
|
||||
/sfdisk
|
||||
|
|
14
configure.ac
14
configure.ac
|
@ -879,6 +879,20 @@ if test "x$build_nsenter" = xyes; then
|
|||
AC_CHECK_FUNCS([setns])
|
||||
fi
|
||||
|
||||
dnl setpriv depends on libcap-ng. It would be possible to build
|
||||
dnl a version of setpriv with limited functionality without libcap-ng,
|
||||
dnl but this isn't currently supported.
|
||||
UL_CHECK_LIB([cap-ng], [capng_apply], [cap_ng])
|
||||
AC_ARG_ENABLE([setpriv],
|
||||
AS_HELP_STRING([--disable-setpriv], [do not build setpriv]),
|
||||
[], enable_setpriv=check
|
||||
)
|
||||
UL_BUILD_INIT([setpriv])
|
||||
UL_REQUIRES_LINUX([setpriv])
|
||||
UL_REQUIRES_HAVE([setpriv], [cap_ng], [libcap-ng])
|
||||
AM_CONDITIONAL(BUILD_SETPRIV, test "x$build_setpriv" = xyes)
|
||||
|
||||
|
||||
AC_ARG_ENABLE([arch],
|
||||
AS_HELP_STRING([--enable-arch], [do build arch]),
|
||||
[], enable_arch=no
|
||||
|
|
|
@ -318,3 +318,10 @@ if HAVE_AUDIT
|
|||
hwclock_LDADD += -laudit
|
||||
endif
|
||||
endif # BUILD_HWCLOCK
|
||||
|
||||
if BUILD_SETPRIV
|
||||
usrbin_exec_PROGRAMS += setpriv
|
||||
dist_man_MANS += sys-utils/setpriv.1
|
||||
setpriv_SOURCES = sys-utils/setpriv.c
|
||||
setpriv_LDADD = $(LDADD) -lcap-ng libcommon.la
|
||||
endif
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
.TH SETPRIV 1 "January 2013" "util-linux" "User Commands"
|
||||
.SH NAME
|
||||
setpriv \- run a program with different Linux privilege settings
|
||||
.SH SYNOPSIS
|
||||
.B setpriv
|
||||
.RI [ options ]
|
||||
program
|
||||
.RI [ arguments ]
|
||||
.SH DESCRIPTION
|
||||
Sets or queries various Linux privilege settings that are inherited across
|
||||
.BR execve (2).
|
||||
.SH OPTION
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-dump\fR
|
||||
Dumps current privilege state. Specify more than once to show extra, mostly
|
||||
useless, information. Incompatible with all other options.
|
||||
.TP
|
||||
\fB\-\-no\-new\-privs\fR
|
||||
Sets the
|
||||
.I no_\:new_\:privs
|
||||
bit. With this bit set,
|
||||
.BR execve (2)
|
||||
will not grant new privileges. For example, the setuid and setgid bits as well
|
||||
as file capabilities will be disabled. (Executing binaries with these bits set
|
||||
will still work, but they will not gain privilege. Certain LSMs, especially
|
||||
AppArmor, may result in failures to execute certain programs.) This bit is
|
||||
inherited by child processes and cannot be unset. See
|
||||
.BR prctl (2)
|
||||
and
|
||||
.IR Documentation/\:prctl/\:no_\:new_\:privs.txt
|
||||
in the Linux kernel source.
|
||||
.IP
|
||||
The no_\:new_\:privs bit is supported since Linux 3.5.
|
||||
.TP
|
||||
\fB\-\-inh\-caps\fR \fI(+|\-)cap\fR,\fI...\fR or \fB\-\-bounding\-set\fR \fI(+|\-)cap\fR,\fI...\fR
|
||||
Sets inheritable capabilities or capability bounding set. See
|
||||
.BR capabilities (7).
|
||||
The argument is a comma-separated list of
|
||||
.I +cap
|
||||
and
|
||||
.I \-cap
|
||||
entries, which add or remove an entry respectively.
|
||||
.I +all
|
||||
and
|
||||
.I \-all
|
||||
can be used to add or remove all caps. The set of capabilities starts out as
|
||||
the current inheritable set for
|
||||
.B \-\-\:inh\-\:caps
|
||||
and the current bounding set for
|
||||
.BR \-\-\:bounding\-\:set .
|
||||
If you drop something from the bounding set without also dropping it from the
|
||||
inheritable set, you are likely to become confused. Do not do that.
|
||||
.TP
|
||||
.BR \-\-list\-caps
|
||||
Lists all known capabilities. Must be specified alone.
|
||||
.TP
|
||||
\fB\-\-ruid\fR \fIuid\fR, \fB\-\-euid\fR \fIuid\fR, \fB\-\-reuid\fR \fIuid\fR
|
||||
Sets the real, effective, or both \fIuid\fRs.
|
||||
.IP
|
||||
Setting
|
||||
.I uid
|
||||
or
|
||||
.I gid
|
||||
does not change capabilities, although the exec call at the end might change
|
||||
capabilities. This means that, if you are root, you probably want to do
|
||||
something like:
|
||||
.IP
|
||||
\-\-reuid=1000 \-\-\:regid=1000 \-\-\:caps=\-\:all
|
||||
.TP
|
||||
\fB\-\-rgid\fR \fIgid\fR, \fB\-\-egid\fR \fIgid\fR, \fB\-\-regid\fR \fIgid\fR
|
||||
Sets the real, effective, or both \fIgid\fRs.
|
||||
.IP
|
||||
For safety, you must specify one of \-\-\:keep\-\:groups,
|
||||
\-\-\:clear\-\:groups, or \-\-\:groups if you set any primary
|
||||
.IR gid .
|
||||
.TP
|
||||
.BR \-\-clear\-groups
|
||||
Clears supplementary groups.
|
||||
.TP
|
||||
\fB\-\-keep\-groups\fR
|
||||
Preserves supplementary groups. Only useful in conjunction with \-\-rgid,
|
||||
\-\-egid, or \-\-regid.
|
||||
.TP
|
||||
\fB\-\-groups\fR \fIgroup\fR,\fI...\fR
|
||||
Sets supplementary groups.
|
||||
.TP
|
||||
\fB\-\-securebits\fR \fI(+|\-)securebit\fR,\fI...\fR
|
||||
Sets or clears securebits. The valid securebits are
|
||||
.IR noroot ,
|
||||
.IR noroot_\:locked ,
|
||||
.IR no_\:setuid_\:fixup ,
|
||||
.IR no_\:setuid_\:fixup_\:locked ,
|
||||
and
|
||||
.IR keep_\:caps_\:locked .
|
||||
.I keep_\:caps
|
||||
is cleared by
|
||||
.BR execve (2)
|
||||
and is therefore not allowed.
|
||||
.TP
|
||||
\fB\-\-selinux\-label\fR \fIlabel\fR
|
||||
Requests a particular SELinux transition (using a transition on exec, not
|
||||
dyntrans). This will fail and cause
|
||||
.BR setpriv (1)
|
||||
to abort if SELinux is not in use, and the transition may be ignored or cause
|
||||
.BR execve (2)
|
||||
to fail at SELinux's whim. (In particular, this is unlikely to work in
|
||||
conjunction with
|
||||
.IR no_\:new_\:privs .)
|
||||
This is similar to
|
||||
.BR runcon (1).
|
||||
.TP
|
||||
\fB\-\-apparmor\-profile\fR \fIprofile\fR
|
||||
Requests a particular AppArmor profile (using a transition on exec). This will
|
||||
fail and cause
|
||||
.BR setpriv (1)
|
||||
to abort if AppArmor is not in use, and the transition may be ignored or cause
|
||||
.BR execve (2)
|
||||
to fail at AppArmor's whim.
|
||||
.TP
|
||||
\fB\-V\fR, \fB\-\-version\fR
|
||||
Display version information and exit.
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Display help and exit.
|
||||
.SH NOTES
|
||||
If applying any specified option fails,
|
||||
.I program
|
||||
will not be run and
|
||||
.B setpriv
|
||||
will return with exit code 127.
|
||||
.PP
|
||||
Be careful with this tool \-\- it may have unexpected security consequences.
|
||||
For example, setting no_\:new_\:privs and then execing a program that is
|
||||
SELinux\-\:confined (as this tool would do) may prevent the SELinux
|
||||
restrictions from taking effect.
|
||||
.SH SEE ALSO
|
||||
.BR prctl (2)
|
||||
.BR capability (7)
|
||||
.SH AUTHOR
|
||||
.MT luto@amacapital.net
|
||||
Andy Lutomirski
|
||||
.ME
|
||||
.SH AVAILABILITY
|
||||
The
|
||||
.B setpriv
|
||||
command is part of the util-linux package and is available from
|
||||
.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
|
||||
Linux Kernel Archive
|
||||
.UE .
|
|
@ -0,0 +1,814 @@
|
|||
/*
|
||||
* setpriv(1) - set various kernel privilege bits and run something
|
||||
*
|
||||
* Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <cap-ng.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <grp.h>
|
||||
#include <linux/securebits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "bitops.h"
|
||||
#include "c.h"
|
||||
#include "closestream.h"
|
||||
#include "nls.h"
|
||||
#include "optutils.h"
|
||||
#include "strutils.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
#ifndef PR_SET_NO_NEW_PRIVS
|
||||
# define PR_SET_NO_NEW_PRIVS 38
|
||||
#endif
|
||||
#ifndef PR_GET_NO_NEW_PRIVS
|
||||
# define PR_GET_NO_NEW_PRIVS 39
|
||||
#endif
|
||||
|
||||
#define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */
|
||||
|
||||
/*
|
||||
* Note: We are subject to https://bugzilla.redhat.com/show_bug.cgi?id=895105
|
||||
* and we will therefore have problems if new capabilities are added. Once
|
||||
* that bug is fixed, I'll (Andy Lutomirski) submit a corresponding fix to
|
||||
* setpriv. In the mean time, the code here tries to work reasonably well.
|
||||
*/
|
||||
|
||||
struct privctx {
|
||||
/* bit arrays -- see include/bitops.h */
|
||||
unsigned int
|
||||
nnp:1, /* no_new_privs */
|
||||
have_ruid:1, /* real uid */
|
||||
have_euid:1, /* effective uid */
|
||||
have_rgid:1, /* real gid */
|
||||
have_egid:1, /* effective gid */
|
||||
have_groups:1, /* add groups */
|
||||
keep_groups:1, /* keep groups */
|
||||
clear_groups:1, /* remove groups */
|
||||
have_securebits:1; /* remove groups */
|
||||
|
||||
/* uids and gids */
|
||||
uid_t ruid, euid;
|
||||
gid_t rgid, egid;
|
||||
|
||||
/* supplementary groups */
|
||||
size_t num_groups;
|
||||
gid_t *groups;
|
||||
|
||||
/* caps */
|
||||
const char *caps_to_inherit;
|
||||
const char *bounding_set;
|
||||
|
||||
/* securebits */
|
||||
int securebits;
|
||||
|
||||
/* LSMs */
|
||||
const char *selinux_label;
|
||||
const char *apparmor_profile;
|
||||
};
|
||||
|
||||
static void __attribute__((__noreturn__)) usage(FILE *out)
|
||||
{
|
||||
fputs(USAGE_HEADER, out);
|
||||
fprintf(out, _(" %s [options] <program> [args...]\n"), program_invocation_short_name);
|
||||
fputs(USAGE_OPTIONS, out);
|
||||
fputs(_(" -d, --dump show current state (and do not exec anything)\n"), out);
|
||||
fputs(_(" --nnp, --no-new-privs disallow granting new privileges\n"), out);
|
||||
fputs(_(" --inh-caps <caps,...> set inheritable capabilities\n"), out);
|
||||
fputs(_(" --bounding-set <caps> set capability bounding set\n"), out);
|
||||
fputs(_(" --ruid <uid> set real uid\n"), out);
|
||||
fputs(_(" --euid <uid> set effective uid\n"), out);
|
||||
fputs(_(" --rgid <gid> set real gid\n"), out);
|
||||
fputs(_(" --egid <gid> set effective gid\n"), out);
|
||||
fputs(_(" --reuid <uid> set real and effective uid\n"), out);
|
||||
fputs(_(" --regid <gid> set real and effective gid\n"), out);
|
||||
fputs(_(" --clear-groups clear supplementary groups\n"), out);
|
||||
fputs(_(" --keep-groups keep supplementary groups\n"), out);
|
||||
fputs(_(" --groups <group,...> set supplementary groups\n"), out);
|
||||
fputs(_(" --securebits <bits> set securebits\n"), out);
|
||||
fputs(_(" --selinux-label <label> set SELinux label (requires process:transition)\n"), out);
|
||||
fputs(_(" --apparmor-profile <pr> set AppArmor profile (requires onexec permission)\n"), out);
|
||||
fputs(USAGE_SEPARATOR, out);
|
||||
fputs(USAGE_HELP, out);
|
||||
fputs(USAGE_VERSION, out);
|
||||
fputs(USAGE_SEPARATOR, out);
|
||||
fputs(_(" This tool can be dangerous. Read the manpage, and be careful.\n"), out);
|
||||
fprintf(out, USAGE_MAN_TAIL("setpriv(1)"));
|
||||
|
||||
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static int real_cap_last_cap(void)
|
||||
{
|
||||
/* CAP_LAST_CAP is untrustworthy. */
|
||||
static int ret = -1;
|
||||
int matched;
|
||||
FILE *f;
|
||||
|
||||
if (ret != -1)
|
||||
return ret;
|
||||
|
||||
f = fopen("/proc/sys/kernel/cap_last_cap", "r");
|
||||
if (!f) {
|
||||
ret = CAP_LAST_CAP; /* guess */
|
||||
return ret;
|
||||
}
|
||||
|
||||
matched = fscanf(f, "%d", &ret);
|
||||
fclose(f);
|
||||
|
||||
if (matched != 1)
|
||||
ret = CAP_LAST_CAP; /* guess */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Returns the number of capabilities printed. */
|
||||
static int print_caps(FILE *f, capng_type_t which)
|
||||
{
|
||||
int i, n = 0, max = real_cap_last_cap();
|
||||
|
||||
for (i = 0; i <= max; i++) {
|
||||
if (capng_have_capability(which, i)) {
|
||||
const char *name = capng_capability_to_name(i);
|
||||
if (n)
|
||||
fputc(',', f);
|
||||
if (name)
|
||||
fputs(name, f);
|
||||
else
|
||||
/* cap-ng has very poor handling of
|
||||
* CAP_LAST_CAP changes. This is the
|
||||
* best we can do. */
|
||||
printf("cap_%d", i);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static void dump_one_secbit(int *first, int *bits, int bit, const char *name)
|
||||
{
|
||||
if (*bits & bit) {
|
||||
if (!*first)
|
||||
printf(",");
|
||||
else
|
||||
*first = 0;
|
||||
fputs(name, stdout);
|
||||
*bits &= ~bit;
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_securebits(void)
|
||||
{
|
||||
int first = 1;
|
||||
int bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0);
|
||||
|
||||
if (bits < 0) {
|
||||
warnx(_("getting process secure bits failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
printf(_("Securebits: "));
|
||||
|
||||
dump_one_secbit(&first, &bits, SECBIT_NOROOT, "noroot");
|
||||
dump_one_secbit(&first, &bits, SECBIT_NOROOT_LOCKED, "noroot_locked");
|
||||
dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP,
|
||||
"no_setuid_fixup");
|
||||
dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP_LOCKED,
|
||||
"no_setuid_fixup_locked");
|
||||
bits &= ~SECBIT_KEEP_CAPS;
|
||||
dump_one_secbit(&first, &bits, SECBIT_KEEP_CAPS_LOCKED,
|
||||
"keep_caps_locked");
|
||||
if (bits) {
|
||||
if (!first)
|
||||
printf(",");
|
||||
else
|
||||
first = 0;
|
||||
printf("0x%x", (unsigned)bits);
|
||||
}
|
||||
|
||||
if (first)
|
||||
printf(_("[none]\n"));
|
||||
else
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void dump_label(const char *name)
|
||||
{
|
||||
char buf[4097];
|
||||
ssize_t len;
|
||||
int fd, e;
|
||||
|
||||
fd = open("/proc/self/attr/current", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
warnx(_("cannot open %s"), "/proc/self/attr/current");
|
||||
return;
|
||||
}
|
||||
|
||||
len = read(fd, buf, sizeof(buf));
|
||||
e = errno;
|
||||
close(fd);
|
||||
if (len < 0) {
|
||||
errno = e;
|
||||
warnx(_("read failed: %s"), name);
|
||||
return;
|
||||
}
|
||||
if (sizeof(buf) - 1 <= (size_t)len) {
|
||||
warnx(_("%s: too long"), name);
|
||||
return;
|
||||
}
|
||||
|
||||
buf[len] = 0;
|
||||
if (0 < len && buf[len - 1] == '\n')
|
||||
buf[len - 1] = 0;
|
||||
printf("%s: %s\n", name, buf);
|
||||
}
|
||||
|
||||
static void dump_groups(void)
|
||||
{
|
||||
int n = getgroups(0, 0);
|
||||
gid_t *groups;
|
||||
if (n < 0) {
|
||||
warn("getgroups failed");
|
||||
return;
|
||||
}
|
||||
|
||||
groups = alloca(n * sizeof(gid_t));
|
||||
n = getgroups(n, groups);
|
||||
if (n < 0) {
|
||||
warn("getgroups failed");
|
||||
return;
|
||||
}
|
||||
|
||||
printf(_("Supplementary groups: "));
|
||||
if (n == 0)
|
||||
printf(_("[none]"));
|
||||
else {
|
||||
int i;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (0 < i)
|
||||
printf(",");
|
||||
printf("%ld", (long)groups[i]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void dump(int dumplevel)
|
||||
{
|
||||
int x;
|
||||
uid_t ru, eu, su;
|
||||
gid_t rg, eg, sg;
|
||||
|
||||
if (getresuid(&ru, &eu, &su) == 0) {
|
||||
printf(_("uid: %u\n"), ru);
|
||||
printf(_("euid: %u\n"), eu);
|
||||
/* Saved and fs uids always equal euid. */
|
||||
if (3 <= dumplevel)
|
||||
printf(_("suid: %u\n"), su);
|
||||
} else
|
||||
warn(_("getresuid failed"));
|
||||
|
||||
if (getresgid(&rg, &eg, &sg) == 0) {
|
||||
printf("gid: %ld\n", (long)rg);
|
||||
printf("egid: %ld\n", (long)eg);
|
||||
/* Saved and fs gids always equal egid. */
|
||||
if (dumplevel >= 3)
|
||||
printf("sgid: %ld\n", (long)sg);
|
||||
} else
|
||||
warn(_("getresgid failed"));
|
||||
|
||||
dump_groups();
|
||||
|
||||
x = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
|
||||
if (0 <= x)
|
||||
printf("no_new_privs: %d\n", x);
|
||||
else
|
||||
warn("setting no_new_privs failed");
|
||||
|
||||
if (2 <= dumplevel) {
|
||||
printf(_("Effective capabilities: "));
|
||||
if (print_caps(stdout, CAPNG_EFFECTIVE) == 0)
|
||||
printf(_("[none]"));
|
||||
printf("\n");
|
||||
|
||||
printf(_("Permitted capabilities: "));
|
||||
if (print_caps(stdout, CAPNG_PERMITTED) == 0)
|
||||
printf(_("[none]"));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf(_("Inheritable capabilities: "));
|
||||
if (print_caps(stdout, CAPNG_INHERITABLE) == 0)
|
||||
printf(_("[none]"));
|
||||
printf("\n");
|
||||
|
||||
printf(_("Capability bounding set: "));
|
||||
if (print_caps(stdout, CAPNG_BOUNDING_SET) == 0)
|
||||
printf(_("[none]"));
|
||||
printf("\n");
|
||||
|
||||
dump_securebits();
|
||||
|
||||
if (access("/sys/fs/selinux", F_OK) == 0)
|
||||
dump_label(_("SELinux label"));
|
||||
|
||||
if (access("/sys/kernel/security/apparmor", F_OK) == 0) {
|
||||
dump_label(_("AppArmor profile"));
|
||||
}
|
||||
}
|
||||
|
||||
static void list_known_caps(void)
|
||||
{
|
||||
int i, max = real_cap_last_cap();
|
||||
|
||||
for (i = 0; i <= max; i++) {
|
||||
const char *name = capng_capability_to_name(i);
|
||||
if (name)
|
||||
printf("%s\n", name);
|
||||
else
|
||||
warnx(_("cap %d: libcap-ng is broken"), i);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_groups(struct privctx *opts, const char *str)
|
||||
{
|
||||
char *groups = xstrdup(str);
|
||||
char *buf = groups; /* We'll reuse it */
|
||||
char *c;
|
||||
size_t i = 0;
|
||||
|
||||
opts->have_groups = 1;
|
||||
opts->num_groups = 0;
|
||||
while ((c = strsep(&groups, ",")))
|
||||
opts->num_groups++;
|
||||
|
||||
/* Start again */
|
||||
strcpy(buf, str); /* It's exactly the right length */
|
||||
groups = buf;
|
||||
|
||||
opts->groups = xcalloc(opts->num_groups, sizeof(gid_t));
|
||||
while ((c = strsep(&groups, ",")))
|
||||
opts->groups[i++] = (gid_t) strtol_or_err(c,
|
||||
_("Invalid supplementary group id"));
|
||||
|
||||
free(groups);
|
||||
}
|
||||
|
||||
static void do_setresuid(const struct privctx *opts)
|
||||
{
|
||||
uid_t ruid, euid, suid;
|
||||
if (getresuid(&ruid, &euid, &suid) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("getresuid failed"));
|
||||
if (opts->have_ruid)
|
||||
ruid = opts->ruid;
|
||||
if (opts->have_euid)
|
||||
euid = opts->euid;
|
||||
|
||||
/* Also copy effective to saved (for paranoia). */
|
||||
if (setresuid(ruid, euid, euid) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("setresuid failed"));
|
||||
}
|
||||
|
||||
static void do_setresgid(const struct privctx *opts)
|
||||
{
|
||||
gid_t rgid, egid, sgid;
|
||||
if (getresgid(&rgid, &egid, &sgid) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("getresgid failed"));
|
||||
if (opts->have_rgid)
|
||||
rgid = opts->rgid;
|
||||
if (opts->have_egid)
|
||||
egid = opts->egid;
|
||||
|
||||
/* Also copy effective to saved (for paranoia). */
|
||||
if (setresgid(rgid, egid, egid) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("setresgid failed"));
|
||||
}
|
||||
|
||||
static void bump_cap(unsigned int cap)
|
||||
{
|
||||
if (capng_have_capability(CAPNG_PERMITTED, cap))
|
||||
capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, cap);
|
||||
}
|
||||
|
||||
static void do_caps(capng_type_t type, const char *caps)
|
||||
{
|
||||
char *my_caps = xstrdup(caps);
|
||||
char *c;
|
||||
|
||||
while ((c = strsep(&my_caps, ","))) {
|
||||
capng_act_t action;
|
||||
if (*c == '+')
|
||||
action = CAPNG_ADD;
|
||||
else if (*c == '-')
|
||||
action = CAPNG_DROP;
|
||||
else
|
||||
errx(EXIT_FAILURE, _("bad capability string"));
|
||||
|
||||
if (!strcmp(c + 1, "all")) {
|
||||
int i;
|
||||
/* It would be really bad if -all didn't drop all
|
||||
* caps. It's better to just fail. */
|
||||
if (real_cap_last_cap() > CAP_LAST_CAP)
|
||||
errx(SETPRIV_EXIT_PRIVERR,
|
||||
_("libcap-ng is too old for \"all\" caps"));
|
||||
for (i = 0; i <= CAP_LAST_CAP; i++)
|
||||
capng_update(action, type, i);
|
||||
} else {
|
||||
int cap = capng_name_to_capability(c + 1);
|
||||
if (0 <= cap)
|
||||
capng_update(action, type, cap);
|
||||
else
|
||||
errx(EXIT_FAILURE,
|
||||
_("unknown capability \"%s\""), c + 1);
|
||||
}
|
||||
}
|
||||
|
||||
free(my_caps);
|
||||
}
|
||||
|
||||
static void parse_securebits(struct privctx *opts, const char *arg)
|
||||
{
|
||||
char *buf = xstrdup(arg);
|
||||
char *c;
|
||||
|
||||
opts->have_securebits = 1;
|
||||
opts->securebits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0);
|
||||
if (opts->securebits < 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("getting process secure bits failed"));
|
||||
|
||||
if (opts->securebits & ~(int)(SECBIT_NOROOT |
|
||||
SECBIT_NOROOT_LOCKED |
|
||||
SECBIT_NO_SETUID_FIXUP |
|
||||
SECBIT_NO_SETUID_FIXUP_LOCKED |
|
||||
SECBIT_KEEP_CAPS |
|
||||
SECBIT_KEEP_CAPS_LOCKED))
|
||||
errx(SETPRIV_EXIT_PRIVERR,
|
||||
_("unrecognized securebit set -- refusing to adjust"));
|
||||
|
||||
while ((c = strsep(&buf, ","))) {
|
||||
if (*c != '+' && *c != '-')
|
||||
errx(EXIT_FAILURE, _("bad securebits string"));
|
||||
|
||||
if (!strcmp(c + 1, "all")) {
|
||||
if (*c == '-')
|
||||
opts->securebits = 0;
|
||||
else
|
||||
errx(EXIT_FAILURE,
|
||||
_("+all securebits is not allowed"));
|
||||
} else {
|
||||
int bit;
|
||||
if (!strcmp(c + 1, "noroot"))
|
||||
bit = SECBIT_NOROOT;
|
||||
else if (!strcmp(c + 1, "noroot_locked"))
|
||||
bit = SECBIT_NOROOT_LOCKED;
|
||||
else if (!strcmp(c + 1, "no_setuid_fixup"))
|
||||
bit = SECBIT_NO_SETUID_FIXUP;
|
||||
else if (!strcmp(c + 1, "no_setuid_fixup_locked"))
|
||||
bit = SECBIT_NO_SETUID_FIXUP_LOCKED;
|
||||
else if (!strcmp(c + 1, "keep_caps"))
|
||||
errx(EXIT_FAILURE,
|
||||
_("adjusting keep_caps does not make sense"));
|
||||
else if (!strcmp(c + 1, "keep_caps_locked"))
|
||||
bit = SECBIT_KEEP_CAPS_LOCKED; /* sigh */
|
||||
else
|
||||
errx(EXIT_FAILURE, _("unrecognized securebit"));
|
||||
|
||||
if (*c == '+')
|
||||
opts->securebits |= bit;
|
||||
else
|
||||
opts->securebits &= ~bit;
|
||||
}
|
||||
}
|
||||
|
||||
opts->securebits |= SECBIT_KEEP_CAPS; /* We need it, and it's reset on exec */
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void do_selinux_label(const char *label)
|
||||
{
|
||||
int fd;
|
||||
size_t len;
|
||||
|
||||
if (access("/sys/fs/selinux", F_OK) != 0)
|
||||
errx(SETPRIV_EXIT_PRIVERR, _("SELinux is not running"));
|
||||
|
||||
fd = open("/proc/self/attr/exec", O_RDWR);
|
||||
if (fd == -1)
|
||||
err(SETPRIV_EXIT_PRIVERR,
|
||||
_("cannot open %s"), "/proc/self/attr/exec");
|
||||
|
||||
len = strlen(label);
|
||||
errno = 0;
|
||||
if (write(fd, label, len) != (ssize_t) len)
|
||||
err(SETPRIV_EXIT_PRIVERR,
|
||||
_("write failed: %s"), "/proc/self/attr/exec");
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void do_apparmor_profile(const char *label)
|
||||
{
|
||||
FILE *f;
|
||||
|
||||
if (access("/sys/kernel/security/apparmor", F_OK) != 0)
|
||||
errx(SETPRIV_EXIT_PRIVERR, _("AppArmor is not running"));
|
||||
|
||||
f = fopen("/proc/self/attr/exec", "wx");
|
||||
if (!f)
|
||||
err(SETPRIV_EXIT_PRIVERR,
|
||||
_("cannot open %s"), "/proc/self/attr/exec");
|
||||
|
||||
if (fprintf(f, "changeprofile %s", label) < 0 || fflush(f) != 0
|
||||
|| fclose(f) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR,
|
||||
_("write failed: %s"), "/proc/self/attr/exec");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
enum {
|
||||
NNP = CHAR_MAX + 1,
|
||||
RUID,
|
||||
EUID,
|
||||
RGID,
|
||||
EGID,
|
||||
REUID,
|
||||
REGID,
|
||||
CLEAR_GROUPS,
|
||||
KEEP_GROUPS,
|
||||
GROUPS,
|
||||
INHCAPS,
|
||||
LISTCAPS,
|
||||
CAPBSET,
|
||||
SECUREBITS,
|
||||
SELINUX_LABEL,
|
||||
APPARMOR_PROFILE
|
||||
};
|
||||
|
||||
static const struct option longopts[] = {
|
||||
{"dump", no_argument, 0, 'd'},
|
||||
{"nnp", no_argument, 0, NNP},
|
||||
{"no-new-privs", no_argument, 0, NNP},
|
||||
{"inh-caps", required_argument, 0, INHCAPS},
|
||||
{"list-caps", no_argument, 0, LISTCAPS},
|
||||
{"ruid", required_argument, 0, RUID},
|
||||
{"euid", required_argument, 0, EUID},
|
||||
{"rgid", required_argument, 0, RGID},
|
||||
{"egid", required_argument, 0, EGID},
|
||||
{"reuid", required_argument, 0, REUID},
|
||||
{"regid", required_argument, 0, REGID},
|
||||
{"clear-groups", no_argument, 0, CLEAR_GROUPS},
|
||||
{"keep-groups", no_argument, 0, KEEP_GROUPS},
|
||||
{"groups", required_argument, 0, GROUPS},
|
||||
{"bounding-set", required_argument, 0, CAPBSET},
|
||||
{"securebits", required_argument, 0, SECUREBITS},
|
||||
{"selinux-label", required_argument, 0, SELINUX_LABEL},
|
||||
{"apparmor-profile", required_argument, 0, APPARMOR_PROFILE},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"version", no_argument, 0, 'V'},
|
||||
{NULL, 0, 0, 0}
|
||||
};
|
||||
|
||||
static const ul_excl_t excl[] = {
|
||||
/* keep in same order with enum definitions */
|
||||
{CLEAR_GROUPS, KEEP_GROUPS, GROUPS},
|
||||
{0}
|
||||
};
|
||||
int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
|
||||
|
||||
int c;
|
||||
struct privctx opts;
|
||||
int dumplevel = 0;
|
||||
int total_opts = 0;
|
||||
int list_caps = 0;
|
||||
|
||||
setlocale(LC_MESSAGES, "");
|
||||
bindtextdomain(PACKAGE, LOCALEDIR);
|
||||
textdomain(PACKAGE);
|
||||
atexit(close_stdout);
|
||||
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
|
||||
while ((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) {
|
||||
err_exclusive_options(c, longopts, excl, excl_st);
|
||||
total_opts++;
|
||||
switch (c) {
|
||||
case 'd':
|
||||
dumplevel++;
|
||||
break;
|
||||
case NNP:
|
||||
if (opts.nnp)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --no-new-privs option"));
|
||||
opts.nnp = 1;
|
||||
break;
|
||||
case RUID:
|
||||
if (opts.have_ruid)
|
||||
errx(EXIT_FAILURE, _("duplicate ruid"));
|
||||
opts.have_ruid = 1;
|
||||
opts.ruid = strtol_or_err(optarg,
|
||||
_("failed to parse ruid"));
|
||||
break;
|
||||
case EUID:
|
||||
if (opts.have_euid)
|
||||
errx(EXIT_FAILURE, _("duplicate euid"));
|
||||
opts.have_euid = 1;
|
||||
opts.euid = strtol_or_err(optarg,
|
||||
_("failed to parse euid"));
|
||||
break;
|
||||
case REUID:
|
||||
if (opts.have_ruid || opts.have_euid)
|
||||
errx(EXIT_FAILURE, _("duplicate ruid or euid"));
|
||||
opts.have_ruid = opts.have_euid = 1;
|
||||
opts.ruid = opts.euid = strtol_or_err(optarg,
|
||||
_("failed to parse reuid"));
|
||||
break;
|
||||
case RGID:
|
||||
if (opts.have_rgid)
|
||||
errx(EXIT_FAILURE, _("duplicate rgid"));
|
||||
opts.have_rgid = 1;
|
||||
opts.rgid = strtol_or_err(optarg,
|
||||
_("failed to parse rgid"));
|
||||
break;
|
||||
case EGID:
|
||||
if (opts.have_egid)
|
||||
errx(EXIT_FAILURE, _("duplicate egid"));
|
||||
opts.have_egid = 1;
|
||||
opts.egid = strtol_or_err(optarg,
|
||||
_("failed to parse egid"));
|
||||
break;
|
||||
case REGID:
|
||||
if (opts.have_rgid || opts.have_egid)
|
||||
errx(EXIT_FAILURE, _("duplicate rgid or egid"));
|
||||
opts.have_rgid = opts.have_egid = 1;
|
||||
opts.rgid = opts.egid = strtol_or_err(optarg,
|
||||
_("failed to parse regid"));
|
||||
break;
|
||||
case CLEAR_GROUPS:
|
||||
if (opts.clear_groups)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --clear-groups option"));
|
||||
opts.clear_groups = 1;
|
||||
break;
|
||||
case KEEP_GROUPS:
|
||||
if (opts.keep_groups)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --keep-groups option"));
|
||||
opts.keep_groups = 1;
|
||||
break;
|
||||
case GROUPS:
|
||||
if (opts.have_groups)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --groups option"));
|
||||
parse_groups(&opts, optarg);
|
||||
break;
|
||||
case LISTCAPS:
|
||||
list_caps = 1;
|
||||
break;
|
||||
case INHCAPS:
|
||||
if (opts.caps_to_inherit)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --caps option"));
|
||||
opts.caps_to_inherit = optarg;
|
||||
break;
|
||||
case CAPBSET:
|
||||
if (opts.bounding_set)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --bounding-set option"));
|
||||
opts.bounding_set = optarg;
|
||||
break;
|
||||
case SECUREBITS:
|
||||
if (opts.have_securebits)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --securebits option"));
|
||||
parse_securebits(&opts, optarg);
|
||||
break;
|
||||
case SELINUX_LABEL:
|
||||
if (opts.selinux_label)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --selinux-label option"));
|
||||
opts.selinux_label = optarg;
|
||||
break;
|
||||
case APPARMOR_PROFILE:
|
||||
if (opts.apparmor_profile)
|
||||
errx(EXIT_FAILURE,
|
||||
_("duplicate --apparmor-profile option"));
|
||||
opts.apparmor_profile = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
usage(stdout);
|
||||
case 'V':
|
||||
printf(UTIL_LINUX_VERSION);
|
||||
return EXIT_SUCCESS;
|
||||
case '?':
|
||||
usage(stderr);
|
||||
default:
|
||||
errx(EXIT_FAILURE, _("unrecognized option '%c'"), c);
|
||||
}
|
||||
}
|
||||
|
||||
if (dumplevel) {
|
||||
if (total_opts != dumplevel || optind < argc)
|
||||
errx(EXIT_FAILURE,
|
||||
_("--dump is incompatible with all other options"));
|
||||
dump(dumplevel);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (list_caps) {
|
||||
if (total_opts != 1 || optind < argc)
|
||||
errx(EXIT_FAILURE,
|
||||
_("--list-caps must be specified alone"));
|
||||
list_known_caps();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (argc <= optind)
|
||||
errx(EXIT_FAILURE, _("No program specified"));
|
||||
|
||||
if ((opts.have_rgid || opts.have_egid)
|
||||
&& !opts.keep_groups && !opts.clear_groups && !opts.have_groups)
|
||||
errx(EXIT_FAILURE,
|
||||
_("--[re]gid requires --keep-groups, --clear-groups, or --groups"));
|
||||
|
||||
if (opts.nnp)
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
|
||||
err(EXIT_FAILURE, _("disallow granting new privileges failed"));
|
||||
|
||||
if (opts.selinux_label)
|
||||
do_selinux_label(opts.selinux_label);
|
||||
if (opts.apparmor_profile)
|
||||
do_apparmor_profile(opts.apparmor_profile);
|
||||
|
||||
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1)
|
||||
err(EXIT_FAILURE, _("keep process capabilities failed"));
|
||||
|
||||
/* We're going to want CAP_SETPCAP, CAP_SETUID, and CAP_SETGID if
|
||||
* possible. */
|
||||
bump_cap(CAP_SETPCAP);
|
||||
bump_cap(CAP_SETUID);
|
||||
bump_cap(CAP_SETGID);
|
||||
if (capng_apply(CAPNG_SELECT_CAPS) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("activate capabilities"));
|
||||
|
||||
if (opts.have_ruid || opts.have_euid) {
|
||||
do_setresuid(&opts);
|
||||
/* KEEPCAPS doesn't work for the effective mask. */
|
||||
if (capng_apply(CAPNG_SELECT_CAPS) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("reactivate capabilities"));
|
||||
}
|
||||
|
||||
if (opts.have_rgid || opts.have_egid)
|
||||
do_setresgid(&opts);
|
||||
|
||||
if (opts.have_groups) {
|
||||
if (setgroups(opts.num_groups, opts.groups) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("setgroups failed"));
|
||||
} else if (opts.clear_groups) {
|
||||
gid_t x = 0;
|
||||
if (setgroups(0, &x) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("setgroups failed"));
|
||||
}
|
||||
|
||||
if (opts.have_securebits)
|
||||
if (prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("set procecess securebits failed"));
|
||||
|
||||
if (opts.bounding_set) {
|
||||
do_caps(CAPNG_BOUNDING_SET, opts.bounding_set);
|
||||
errno = EPERM; /* capng doesn't set errno if we're missing CAP_SETPCAP */
|
||||
if (capng_apply(CAPNG_SELECT_BOUNDS) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("apply bounding set"));
|
||||
}
|
||||
|
||||
if (opts.caps_to_inherit) {
|
||||
do_caps(CAPNG_INHERITABLE, opts.caps_to_inherit);
|
||||
if (capng_apply(CAPNG_SELECT_CAPS) != 0)
|
||||
err(SETPRIV_EXIT_PRIVERR, _("apply capabilities"));
|
||||
}
|
||||
|
||||
execvp(argv[optind], argv + optind);
|
||||
|
||||
err(EXIT_FAILURE, _("cannot execute: %s"), argv[optind]);
|
||||
}
|
Loading…
Reference in New Issue