diff options
Diffstat (limited to '')
-rw-r--r-- | sys-utils/setpriv.c | 1067 |
1 files changed, 1067 insertions, 0 deletions
diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c new file mode 100644 index 0000000..f8a0364 --- /dev/null +++ b/sys-utils/setpriv.c @@ -0,0 +1,1067 @@ +/* + * 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 <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include "c.h" +#include "caputils.h" +#include "closestream.h" +#include "nls.h" +#include "optutils.h" +#include "strutils.h" +#include "xalloc.h" +#include "pathnames.h" +#include "signames.h" +#include "env.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 */ + +/* The shell to set SHELL env.variable if none is given in the user's passwd entry. */ +#define DEFAULT_SHELL "/bin/sh" + +static gid_t get_group(const char *s, const char *err); + +enum cap_type { + CAP_TYPE_EFFECTIVE = CAPNG_EFFECTIVE, + CAP_TYPE_PERMITTED = CAPNG_PERMITTED, + CAP_TYPE_INHERITABLE = CAPNG_INHERITABLE, + CAP_TYPE_BOUNDING = CAPNG_BOUNDING_SET, + CAP_TYPE_AMBIENT = (1 << 4) +}; + +/* + * 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 { + 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_passwd:1, /* passwd entry */ + have_groups:1, /* add groups */ + keep_groups:1, /* keep groups */ + clear_groups:1, /* remove groups */ + init_groups:1, /* initialize groups */ + reset_env:1, /* reset environment */ + have_securebits:1; /* remove groups */ + + /* uids and gids */ + uid_t ruid, euid; + gid_t rgid, egid; + + /* real user passwd entry */ + struct passwd passwd; + + /* supplementary groups */ + size_t num_groups; + gid_t *groups; + + /* caps */ + const char *caps_to_inherit; + const char *ambient_caps; + const char *bounding_set; + + /* securebits */ + int securebits; + /* parent death signal (<0 clear, 0 nothing, >0 signal) */ + int pdeathsig; + + /* LSMs */ + const char *selinux_label; + const char *apparmor_profile; +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <program> [<argument>...]\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Run a program with different privilege settings.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -d, --dump show current state (and do not exec)\n"), out); + fputs(_(" --nnp, --no-new-privs disallow granting new privileges\n"), out); + fputs(_(" --ambient-caps <caps,...> set ambient capabilities\n"), out); + fputs(_(" --inh-caps <caps,...> set inheritable capabilities\n"), out); + fputs(_(" --bounding-set <caps> set capability bounding set\n"), out); + fputs(_(" --ruid <uid|user> set real uid\n"), out); + fputs(_(" --euid <uid|user> set effective uid\n"), out); + fputs(_(" --rgid <gid|user> set real gid\n"), out); + fputs(_(" --egid <gid|group> set effective gid\n"), out); + fputs(_(" --reuid <uid|user> set real and effective uid\n"), out); + fputs(_(" --regid <gid|group> set real and effective gid\n"), out); + fputs(_(" --clear-groups clear supplementary groups\n"), out); + fputs(_(" --keep-groups keep supplementary groups\n"), out); + fputs(_(" --init-groups initialize supplementary groups\n"), out); + fputs(_(" --groups <group,...> set supplementary groups by UID or name\n"), out); + fputs(_(" --securebits <bits> set securebits\n"), out); + fputs(_(" --pdeathsig keep|clear|<signame>\n" + " set or clear parent death signal\n"), out); + fputs(_(" --selinux-label <label> set SELinux label\n"), out); + fputs(_(" --apparmor-profile <pr> set AppArmor profile\n"), out); + fputs(_(" --reset-env clear all environment and initialize\n" + " HOME, SHELL, USER, LOGNAME and PATH\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(29)); + fputs(USAGE_SEPARATOR, out); + fputs(_(" This tool can be dangerous. Read the manpage, and be careful.\n"), out); + printf(USAGE_MAN_TAIL("setpriv(1)")); + + exit(EXIT_SUCCESS); +} + +static int has_cap(enum cap_type which, unsigned int i) +{ + switch (which) { + case CAP_TYPE_EFFECTIVE: + case CAP_TYPE_BOUNDING: + case CAP_TYPE_INHERITABLE: + case CAP_TYPE_PERMITTED: + return capng_have_capability((capng_type_t)which, i); + case CAP_TYPE_AMBIENT: + return prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, + (unsigned long) i, 0UL, 0UL); + default: + warnx(_("invalid capability type")); + return -1; + } +} + +/* Returns the number of capabilities printed. */ +static int print_caps(FILE *f, enum cap_type which) +{ + int i, n = 0, max = cap_last_cap(); + + for (i = 0; i <= max; i++) { + int ret = has_cap(which, i); + + if (i == 0 && ret < 0) + return -1; + + if (ret == 1) { + 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) + *first = 0; + else + printf(","); + 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) + first = 0; + else + printf(","); + 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(_PATH_PROC_ATTR_CURRENT, O_RDONLY); + if (fd == -1) { + warn(_("cannot open %s"), _PATH_PROC_ATTR_CURRENT); + return; + } + + len = read(fd, buf, sizeof(buf)); + e = errno; + close(fd); + if (len < 0) { + errno = e; + warn(_("cannot read %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, NULL); + gid_t *groups; + + if (n < 0) { + warn("getgroups failed"); + return; + } + + groups = xmalloc(n * sizeof(gid_t)); + n = getgroups(n, groups); + if (n < 0) { + free(groups); + 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"); + free(groups); +} + +static void dump_pdeathsig(void) +{ + int pdeathsig; + + if (prctl(PR_GET_PDEATHSIG, &pdeathsig) != 0) { + warn(_("get pdeathsig failed")); + return; + } + + printf("Parent death signal: "); + if (pdeathsig && signum_to_signame(pdeathsig) != NULL) + printf("%s\n", signum_to_signame(pdeathsig)); + else if (pdeathsig) + printf("%d\n", pdeathsig); + else + printf("[none]\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, CAP_TYPE_EFFECTIVE) == 0) + printf(_("[none]")); + printf("\n"); + + printf(_("Permitted capabilities: ")); + if (print_caps(stdout, CAP_TYPE_PERMITTED) == 0) + printf(_("[none]")); + printf("\n"); + } + + printf(_("Inheritable capabilities: ")); + if (print_caps(stdout, CAP_TYPE_INHERITABLE) == 0) + printf(_("[none]")); + printf("\n"); + + printf(_("Ambient capabilities: ")); + x = print_caps(stdout, CAP_TYPE_AMBIENT); + if (x == 0) + printf(_("[none]")); + if (x < 0) + printf(_("[unsupported]")); + printf("\n"); + + printf(_("Capability bounding set: ")); + if (print_caps(stdout, CAP_TYPE_BOUNDING) == 0) + printf(_("[none]")); + printf("\n"); + + dump_securebits(); + dump_pdeathsig(); + + if (access(_PATH_SYS_SELINUX, F_OK) == 0) + dump_label(_("SELinux label")); + + if (access(_PATH_SYS_APPARMOR, F_OK) == 0) { + dump_label(_("AppArmor profile")); + } +} + +static void list_known_caps(void) +{ + int i, max = 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++] = get_group(c, _("Invalid supplementary group id")); + + free(groups); +} + +static void parse_pdeathsig(struct privctx *opts, const char *str) +{ + if (!strcmp(str, "keep")) { + if (prctl(PR_GET_PDEATHSIG, &opts->pdeathsig) != 0) + errx(SETPRIV_EXIT_PRIVERR, + _("failed to get parent death signal")); + } else if (!strcmp(str, "clear")) { + opts->pdeathsig = -1; + } else if ((opts->pdeathsig = signame_to_signum(str)) < 0) { + errx(EXIT_FAILURE, _("unknown signal: %s"), str); + } +} + +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 int cap_update(capng_act_t action, + enum cap_type type, unsigned int cap) +{ + switch (type) { + case CAP_TYPE_EFFECTIVE: + case CAP_TYPE_BOUNDING: + case CAP_TYPE_INHERITABLE: + case CAP_TYPE_PERMITTED: + return capng_update(action, (capng_type_t) type, cap); + case CAP_TYPE_AMBIENT: + { + int ret; + + if (action == CAPNG_ADD) + ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, + (unsigned long) cap, 0UL, 0UL); + else + ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_LOWER, + (unsigned long) cap, 0UL, 0UL); + + return ret; + } + default: + errx(EXIT_FAILURE, _("unsupported capability type")); + return -1; + } +} + +static void do_caps(enum cap_type 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 (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++) + cap_update(action, type, i); + } else { + int cap = capng_name_to_capability(c + 1); + if (0 <= cap) + cap_update(action, type, cap); + else if (sscanf(c + 1, "cap_%d", &cap) == 1 + && 0 <= cap && cap <= cap_last_cap()) + cap_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(_PATH_SYS_SELINUX, F_OK) != 0) + errx(SETPRIV_EXIT_PRIVERR, _("SELinux is not running")); + + fd = open(_PATH_PROC_ATTR_EXEC, O_RDWR); + if (fd == -1) + err(SETPRIV_EXIT_PRIVERR, + _("cannot open %s"), _PATH_PROC_ATTR_EXEC); + + len = strlen(label); + errno = 0; + if (write(fd, label, len) != (ssize_t) len) + err(SETPRIV_EXIT_PRIVERR, + _("write failed: %s"), _PATH_PROC_ATTR_EXEC); + + if (close(fd) != 0) + err(SETPRIV_EXIT_PRIVERR, + _("close failed: %s"), _PATH_PROC_ATTR_EXEC); +} + +static void do_apparmor_profile(const char *label) +{ + FILE *f; + + if (access(_PATH_SYS_APPARMOR, F_OK) != 0) + errx(SETPRIV_EXIT_PRIVERR, _("AppArmor is not running")); + + f = fopen(_PATH_PROC_ATTR_EXEC, "r+"); + if (!f) + err(SETPRIV_EXIT_PRIVERR, + _("cannot open %s"), _PATH_PROC_ATTR_EXEC); + + fprintf(f, "exec %s", label); + + if (close_stream(f) != 0) + err(SETPRIV_EXIT_PRIVERR, + _("write failed: %s"), _PATH_PROC_ATTR_EXEC); +} + + +static void do_reset_environ(struct passwd *pw) +{ + char *term = getenv("TERM"); + + if (term) + term = xstrdup(term); +#ifdef HAVE_CLEARENV + clearenv(); +#else + environ = NULL; +#endif + if (term) { + xsetenv("TERM", term, 1); + free(term); + } + + if (pw->pw_shell && *pw->pw_shell) + xsetenv("SHELL", pw->pw_shell, 1); + else + xsetenv("SHELL", DEFAULT_SHELL, 1); + + xsetenv("HOME", pw->pw_dir, 1); + xsetenv("USER", pw->pw_name, 1); + xsetenv("LOGNAME", pw->pw_name, 1); + + if (pw->pw_uid) + xsetenv("PATH", _PATH_DEFPATH, 1); + else + xsetenv("PATH", _PATH_DEFPATH_ROOT, 1); +} + +static uid_t get_user(const char *s, const char *err) +{ + struct passwd *pw; + long tmp; + pw = getpwnam(s); + if (pw) + return pw->pw_uid; + tmp = strtol_or_err(s, err); + return tmp; +} + +static gid_t get_group(const char *s, const char *err) +{ + struct group *gr; + long tmp; + gr = getgrnam(s); + if (gr) + return gr->gr_gid; + tmp = strtol_or_err(s, err); + return tmp; +} + +static struct passwd *get_passwd(const char *s, uid_t *uid, const char *err) +{ + struct passwd *pw; + long tmp; + pw = getpwnam(s); + if (pw) { + *uid = pw->pw_uid; + } else { + tmp = strtol_or_err(s, err); + *uid = tmp; + pw = getpwuid(*uid); + } + return pw; +} + +static struct passwd *passwd_copy(struct passwd *dst, const struct passwd *src) +{ + struct passwd *rv; + rv = memcpy(dst, src, sizeof(*dst)); + rv->pw_name = xstrdup(rv->pw_name); + rv->pw_passwd = xstrdup(rv->pw_passwd); + rv->pw_gecos = xstrdup(rv->pw_gecos); + rv->pw_dir = xstrdup(rv->pw_dir); + rv->pw_shell = xstrdup(rv->pw_shell); + return rv; +} + +int main(int argc, char **argv) +{ + enum { + NNP = CHAR_MAX + 1, + RUID, + EUID, + RGID, + EGID, + REUID, + REGID, + CLEAR_GROUPS, + KEEP_GROUPS, + INIT_GROUPS, + GROUPS, + INHCAPS, + AMBCAPS, + LISTCAPS, + CAPBSET, + SECUREBITS, + PDEATHSIG, + SELINUX_LABEL, + APPARMOR_PROFILE, + RESET_ENV + }; + + static const struct option longopts[] = { + { "dump", no_argument, NULL, 'd' }, + { "nnp", no_argument, NULL, NNP }, + { "no-new-privs", no_argument, NULL, NNP }, + { "inh-caps", required_argument, NULL, INHCAPS }, + { "ambient-caps", required_argument, NULL, AMBCAPS }, + { "list-caps", no_argument, NULL, LISTCAPS }, + { "ruid", required_argument, NULL, RUID }, + { "euid", required_argument, NULL, EUID }, + { "rgid", required_argument, NULL, RGID }, + { "egid", required_argument, NULL, EGID }, + { "reuid", required_argument, NULL, REUID }, + { "regid", required_argument, NULL, REGID }, + { "clear-groups", no_argument, NULL, CLEAR_GROUPS }, + { "keep-groups", no_argument, NULL, KEEP_GROUPS }, + { "init-groups", no_argument, NULL, INIT_GROUPS }, + { "groups", required_argument, NULL, GROUPS }, + { "bounding-set", required_argument, NULL, CAPBSET }, + { "securebits", required_argument, NULL, SECUREBITS }, + { "pdeathsig", required_argument, NULL, PDEATHSIG, }, + { "selinux-label", required_argument, NULL, SELINUX_LABEL }, + { "apparmor-profile", required_argument, NULL, APPARMOR_PROFILE }, + { "help", no_argument, NULL, 'h' }, + { "reset-env", no_argument, NULL, RESET_ENV, }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { + /* keep in same order with enum definitions */ + {CLEAR_GROUPS, KEEP_GROUPS, INIT_GROUPS, GROUPS}, + {0} + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + int c; + struct privctx opts; + struct passwd *pw = NULL; + int dumplevel = 0; + int total_opts = 0; + int list_caps = 0; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + 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; + pw = get_passwd(optarg, &opts.ruid, _("failed to parse ruid")); + if (pw) { + passwd_copy(&opts.passwd, pw); + opts.have_passwd = 1; + } + break; + case EUID: + if (opts.have_euid) + errx(EXIT_FAILURE, _("duplicate euid")); + opts.have_euid = 1; + opts.euid = get_user(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; + pw = get_passwd(optarg, &opts.ruid, _("failed to parse reuid")); + opts.euid = opts.ruid; + if (pw) { + passwd_copy(&opts.passwd, pw); + opts.have_passwd = 1; + } + break; + case RGID: + if (opts.have_rgid) + errx(EXIT_FAILURE, _("duplicate rgid")); + opts.have_rgid = 1; + opts.rgid = get_group(optarg, _("failed to parse rgid")); + break; + case EGID: + if (opts.have_egid) + errx(EXIT_FAILURE, _("duplicate egid")); + opts.have_egid = 1; + opts.egid = get_group(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 = get_group(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 INIT_GROUPS: + if (opts.init_groups) + errx(EXIT_FAILURE, + _("duplicate --init-groups option")); + opts.init_groups = 1; + break; + case GROUPS: + if (opts.have_groups) + errx(EXIT_FAILURE, + _("duplicate --groups option")); + parse_groups(&opts, optarg); + break; + case PDEATHSIG: + if (opts.pdeathsig) + errx(EXIT_FAILURE, + _("duplicate --keep-pdeathsig option")); + parse_pdeathsig(&opts, optarg); + break; + case LISTCAPS: + list_caps = 1; + break; + case INHCAPS: + if (opts.caps_to_inherit) + errx(EXIT_FAILURE, + _("duplicate --inh-caps option")); + opts.caps_to_inherit = optarg; + break; + case AMBCAPS: + if (opts.ambient_caps) + errx(EXIT_FAILURE, + _("duplicate --ambient-caps option")); + opts.ambient_caps = 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 RESET_ENV: + opts.reset_env = 1; + break; + + case 'h': + usage(); + case 'V': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + } + + 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.init_groups + && !opts.have_groups) + errx(EXIT_FAILURE, + _("--[re]gid requires --keep-groups, --clear-groups, --init-groups, or --groups")); + + if (opts.init_groups && !opts.have_ruid) + errx(EXIT_FAILURE, + _("--init-groups requires --ruid or --reuid")); + + if (opts.init_groups && !opts.have_passwd) + errx(EXIT_FAILURE, + _("uid %ld not found, --init-groups requires an user that " + "can be found on the system"), + (long) opts.ruid); + + if (opts.reset_env) { + if (opts.have_passwd) + /* pwd according to --ruid or --reuid */ + pw = &opts.passwd; + else + /* pwd for the current user */ + pw = getpwuid(getuid()); + do_reset_environ(pw); + } + + if (opts.nnp && 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.init_groups) { + if (initgroups(opts.passwd.pw_name, opts.passwd.pw_gid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("initgroups 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 && prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0) + err(SETPRIV_EXIT_PRIVERR, _("set process securebits failed")); + + if (opts.bounding_set) { + do_caps(CAP_TYPE_BOUNDING, 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(CAP_TYPE_INHERITABLE, opts.caps_to_inherit); + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("apply capabilities")); + } + + if (opts.ambient_caps) { + do_caps(CAP_TYPE_AMBIENT, opts.ambient_caps); + } + + /* Clear or set parent death signal */ + if (opts.pdeathsig && prctl(PR_SET_PDEATHSIG, opts.pdeathsig < 0 ? 0 : opts.pdeathsig) != 0) + err(SETPRIV_EXIT_PRIVERR, _("set parent death signal failed")); + + execvp(argv[optind], argv + optind); + errexec(argv[optind]); +} |