On Sat, Jan 26, 2013 at 6:29 AM, Sami Kerola <kerolasa@xxxxxx> wrote: > From: Andy Lutomirski <luto@xxxxxxxxxxxxxx> > > > 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@xxxxxx: a lot of small adjustment making the command to be a > good fit in util-linux project] > > Signed-off-by: Sami Kerola <kerolasa@xxxxxx> > Signed-off-by: Andy Lutomirski <luto@xxxxxxxxxxxxxx> > --- > Changes since Andy Lutomirski's V2 at Mon, 14 Jan 2013. > > - Align man page with Documentation/howto-man-page.txt > - Clean up usage output. > - Re-use messages making translators to work less. > - Make some messages easier to understand. > - Drop bool usage, use bit fields instead. > - Fix smatch warnings. > - Remove unnecessary braces where possible. > - Tidy few code style issues. What's the status of this patch? I'm not really familiar with the merge process for util-linux. FWIW, this code would benefit from s/--caps/--inhcaps/g. My bad. I can submit a new version or a followup if that would be helpful. --Andy > > .gitignore | 1 + > configure.ac | 14 + > sys-utils/Makemodule.am | 7 + > sys-utils/setpriv.1 | 149 +++++++++ > sys-utils/setpriv.c | 814 ++++++++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 985 insertions(+) > create mode 100644 sys-utils/setpriv.1 > create mode 100644 sys-utils/setpriv.c > > diff --git a/.gitignore b/.gitignore > index b2e9d6d..7dbb81c 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -150,6 +150,7 @@ tests/run.sh.trs > /script > /scriptreplay > /setarch > +/setpriv > /setsid > /setterm > /sfdisk > diff --git a/configure.ac b/configure.ac > index 9024809..d3a8e9e 100644 > --- a/configure.ac > +++ b/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 > diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am > index 978d97f..86c529e 100644 > --- a/sys-utils/Makemodule.am > +++ b/sys-utils/Makemodule.am > @@ -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 > diff --git a/sys-utils/setpriv.1 b/sys-utils/setpriv.1 > new file mode 100644 > index 0000000..c56d89f > --- /dev/null > +++ b/sys-utils/setpriv.1 > @@ -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@xxxxxxxxxxxxxx > +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 . > diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c > new file mode 100644 > index 0000000..1662daf > --- /dev/null > +++ b/sys-utils/setpriv.c > @@ -0,0 +1,814 @@ > +/* > + * setpriv(1) - set various kernel privilege bits and run something > + * > + * Copyright (C) 2012 Andy Lutomirski <luto@xxxxxxxxxxxxxx> > + * > + * 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]); > +} > -- > 1.8.1.1 > -- Andy Lutomirski AMA Capital Management, LLC -- To unsubscribe from this list: send the line "unsubscribe util-linux" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html