]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
setpriv: implement option to set parent death signal
authorPatrick Steinhardt <ps@pks.im>
Tue, 10 Apr 2018 11:08:21 +0000 (12:08 +0100)
committerKarel Zak <kzak@redhat.com>
Tue, 10 Apr 2018 11:52:21 +0000 (13:52 +0200)
When a process uses the syscall `prctl(PR_SET_PDEATHSIG, ...)`, it will
get notified with a process-defined signal as soon as its parent process
dies. This is for example being used by unshare(1)'s recently added
"--kill-child" option, causing the forked child to be killed as soon as
unshare itself dies.

Unfortunately, some LSMs will cause the parent death signal to be reset
when a process changes credentials, with the most important ones being
SELinux and AppArmor. The following command will thus not work as
expected:

    unshare --fork --kill-child setpriv --reuid user <executable>

As soon as setpriv changes UID, the parent death signal is cleared and
the child will never get signalled when unshare gets killed.

Add a new option "--pdeathsig keep|clear|<signal>". Setting this flag
will cause us to either

- restore the previously active parent death signal as soon as the
  setpriv has applied all credential changes
- clear the parent death signal
- set the parent death signal to "<signal>"

Furthermore, print out the currently set signal when dumping process
state.

[kzak@redhat.com: - small changes in codding style]

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Karel Zak <kzak@redhat.com>
sys-utils/setpriv.1
sys-utils/setpriv.c

index b900f6e082101a5cadf66025fe10a70cea129821..f989bf33cec4f220add141f0c0895cbe381da405 100644 (file)
@@ -139,6 +139,12 @@ is cleared by
 .BR execve (2)
 and is therefore not allowed.
 .TP
+.BR "\-\-pdeathsig keep" | clear | <signal>
+Keep, clear or set the parent death signal.  Some LSMs, most notably SELinux and
+AppArmor, clear the signal when the process' credentials change.  Using
+\fB--pdeathsig keep\fR will restore the parent death signal after changing
+credentials to remedy that situation.
+.TP
 .BI \-\-selinux\-label " label"
 Request a particular SELinux transition (using a transition on exec, not
 dyntrans).  This will fail and cause
index 4147978cc67dbd011b0443bdbb1f5d17fe5b5efd..0d3a3b3c9a08645addbde19b743c9fdf3cfb8687 100644 (file)
@@ -38,6 +38,7 @@
 #include "strutils.h"
 #include "xalloc.h"
 #include "pathnames.h"
+#include "signames.h"
 
 #ifndef PR_SET_NO_NEW_PRIVS
 # define PR_SET_NO_NEW_PRIVS 38
@@ -102,6 +103,8 @@ struct privctx {
 
        /* securebits */
        int securebits;
+       /* parent death signal (<0 clear, 0 nothing, >0 signal) */
+       int pdeathsig;
 
        /* LSMs */
        const char *selinux_label;
@@ -135,6 +138,8 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" --init-groups               initialize supplementary groups\n"), out);
        fputs(_(" --groups <group,...>        set supplementary groups\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);
 
@@ -329,6 +334,24 @@ static void dump_groups(void)
        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;
@@ -392,6 +415,7 @@ static void dump(int dumplevel)
        printf("\n");
 
        dump_securebits();
+       dump_pdeathsig();
 
        if (access(_PATH_SYS_SELINUX, F_OK) == 0)
                dump_label(_("SELinux label"));
@@ -438,6 +462,19 @@ static void parse_groups(struct privctx *opts, const char *str)
        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;
@@ -711,6 +748,7 @@ int main(int argc, char **argv)
                LISTCAPS,
                CAPBSET,
                SECUREBITS,
+               PDEATHSIG,
                SELINUX_LABEL,
                APPARMOR_PROFILE
        };
@@ -734,6 +772,7 @@ int main(int argc, char **argv)
                { "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'              },
@@ -844,6 +883,12 @@ int main(int argc, char **argv)
                                     _("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;
@@ -989,6 +1034,10 @@ int main(int argc, char **argv)
                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]);
 }