]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
setpriv: run a program with different Linux privilege settings
authorAndy Lutomirski <luto@amacapital.net>
Mon, 14 Jan 2013 15:58:57 +0000 (07:58 -0800)
committerKarel Zak <kzak@redhat.com>
Tue, 5 Feb 2013 09:35:33 +0000 (10:35 +0100)
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>
.gitignore
configure.ac
sys-utils/Makemodule.am
sys-utils/setpriv.1 [new file with mode: 0644]
sys-utils/setpriv.c [new file with mode: 0644]

index b2e9d6d54541ceea81eadd6611606bc64b1cf2b0..7dbb81c0bb35d4bec843694a953b5b03dc1b8da2 100644 (file)
@@ -150,6 +150,7 @@ tests/run.sh.trs
 /script
 /scriptreplay
 /setarch
+/setpriv
 /setsid
 /setterm
 /sfdisk
index 9024809e7ef3529fc62c511f8c93f62a30a3250e..d3a8e9e7f97a3c0945b8778f89ef803c27675acb 100644 (file)
@@ -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
index 978d97f7d64aeed70195a4fed0fa65aafe2e0ff0..86c529eae070fe6672eafc83d9f0cd4cc84a19bb 100644 (file)
@@ -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 (file)
index 0000000..c56d89f
--- /dev/null
@@ -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 .
diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c
new file mode 100644 (file)
index 0000000..1662daf
--- /dev/null
@@ -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]);
+}