Re: [PATCH] setpriv: run a program with different Linux privilege settings

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux