25f9c8
From 0b421290e05862e1abbb5a82654bd2de9829dd58 Mon Sep 17 00:00:00 2001
25f9c8
From: Patrick Steinhardt <ps@pks.im>
25f9c8
Date: Tue, 10 Apr 2018 12:08:21 +0100
25f9c8
Subject: [PATCH 69/74] setpriv: implement option to set parent death signal
25f9c8
25f9c8
When a process uses the syscall `prctl(PR_SET_PDEATHSIG, ...)`, it will
25f9c8
get notified with a process-defined signal as soon as its parent process
25f9c8
dies. This is for example being used by unshare(1)'s recently added
25f9c8
"--kill-child" option, causing the forked child to be killed as soon as
25f9c8
unshare itself dies.
25f9c8
25f9c8
Unfortunately, some LSMs will cause the parent death signal to be reset
25f9c8
when a process changes credentials, with the most important ones being
25f9c8
SELinux and AppArmor. The following command will thus not work as
25f9c8
expected:
25f9c8
25f9c8
    unshare --fork --kill-child setpriv --reuid user <executable>
25f9c8
25f9c8
As soon as setpriv changes UID, the parent death signal is cleared and
25f9c8
the child will never get signalled when unshare gets killed.
25f9c8
25f9c8
Add a new option "--pdeathsig keep|clear|<signal>". Setting this flag
25f9c8
will cause us to either
25f9c8
25f9c8
- restore the previously active parent death signal as soon as the
25f9c8
  setpriv has applied all credential changes
25f9c8
- clear the parent death signal
25f9c8
- set the parent death signal to "<signal>"
25f9c8
25f9c8
Furthermore, print out the currently set signal when dumping process
25f9c8
state.
25f9c8
25f9c8
[kzak@redhat.com: - small changes in codding style]
25f9c8
25f9c8
Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1894192
25f9c8
Signed-off-by: Patrick Steinhardt <ps@pks.im>
25f9c8
Signed-off-by: Karel Zak <kzak@redhat.com>
25f9c8
---
25f9c8
 sys-utils/setpriv.1 |  6 ++++++
25f9c8
 sys-utils/setpriv.c | 49 +++++++++++++++++++++++++++++++++++++++++++++
25f9c8
 2 files changed, 55 insertions(+)
25f9c8
25f9c8
diff --git a/sys-utils/setpriv.1 b/sys-utils/setpriv.1
25f9c8
index b900f6e08..f989bf33c 100644
25f9c8
--- a/sys-utils/setpriv.1
25f9c8
+++ b/sys-utils/setpriv.1
25f9c8
@@ -139,6 +139,12 @@ is cleared by
25f9c8
 .BR execve (2)
25f9c8
 and is therefore not allowed.
25f9c8
 .TP
25f9c8
+.BR "\-\-pdeathsig keep" | clear | <signal>
25f9c8
+Keep, clear or set the parent death signal.  Some LSMs, most notably SELinux and
25f9c8
+AppArmor, clear the signal when the process' credentials change.  Using
25f9c8
+\fB--pdeathsig keep\fR will restore the parent death signal after changing
25f9c8
+credentials to remedy that situation.
25f9c8
+.TP
25f9c8
 .BI \-\-selinux\-label " label"
25f9c8
 Request a particular SELinux transition (using a transition on exec, not
25f9c8
 dyntrans).  This will fail and cause
25f9c8
diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c
25f9c8
index 4147978cc..0d3a3b3c9 100644
25f9c8
--- a/sys-utils/setpriv.c
25f9c8
+++ b/sys-utils/setpriv.c
25f9c8
@@ -38,6 +38,7 @@
25f9c8
 #include "strutils.h"
25f9c8
 #include "xalloc.h"
25f9c8
 #include "pathnames.h"
25f9c8
+#include "signames.h"
25f9c8
 
25f9c8
 #ifndef PR_SET_NO_NEW_PRIVS
25f9c8
 # define PR_SET_NO_NEW_PRIVS 38
25f9c8
@@ -102,6 +103,8 @@ struct privctx {
25f9c8
 
25f9c8
 	/* securebits */
25f9c8
 	int securebits;
25f9c8
+	/* parent death signal (<0 clear, 0 nothing, >0 signal) */
25f9c8
+	int pdeathsig;
25f9c8
 
25f9c8
 	/* LSMs */
25f9c8
 	const char *selinux_label;
25f9c8
@@ -135,6 +138,8 @@ static void __attribute__((__noreturn__)) usage(void)
25f9c8
 	fputs(_(" --init-groups               initialize supplementary groups\n"), out);
25f9c8
 	fputs(_(" --groups <group,...>        set supplementary groups\n"), out);
25f9c8
 	fputs(_(" --securebits <bits>         set securebits\n"), out);
25f9c8
+	fputs(_(" --pdeathsig keep|clear|<signame>\n"
25f9c8
+	        "                             set or clear parent death signal\n"), out);
25f9c8
 	fputs(_(" --selinux-label <label>     set SELinux label\n"), out);
25f9c8
 	fputs(_(" --apparmor-profile <pr>     set AppArmor profile\n"), out);
25f9c8
 
25f9c8
@@ -329,6 +334,24 @@ static void dump_groups(void)
25f9c8
 	free(groups);
25f9c8
 }
25f9c8
 
25f9c8
+static void dump_pdeathsig(void)
25f9c8
+{
25f9c8
+	int pdeathsig;
25f9c8
+
25f9c8
+	if (prctl(PR_GET_PDEATHSIG, &pdeathsig) != 0) {
25f9c8
+		warn(_("get pdeathsig failed"));
25f9c8
+		return;
25f9c8
+	}
25f9c8
+
25f9c8
+	printf("Parent death signal: ");
25f9c8
+	if (pdeathsig && signum_to_signame(pdeathsig) != NULL)
25f9c8
+		printf("%s\n", signum_to_signame(pdeathsig));
25f9c8
+	else if (pdeathsig)
25f9c8
+		printf("%d\n", pdeathsig);
25f9c8
+	else
25f9c8
+		printf("[none]\n");
25f9c8
+}
25f9c8
+
25f9c8
 static void dump(int dumplevel)
25f9c8
 {
25f9c8
 	int x;
25f9c8
@@ -392,6 +415,7 @@ static void dump(int dumplevel)
25f9c8
 	printf("\n");
25f9c8
 
25f9c8
 	dump_securebits();
25f9c8
+	dump_pdeathsig();
25f9c8
 
25f9c8
 	if (access(_PATH_SYS_SELINUX, F_OK) == 0)
25f9c8
 		dump_label(_("SELinux label"));
25f9c8
@@ -438,6 +462,19 @@ static void parse_groups(struct privctx *opts, const char *str)
25f9c8
 	free(groups);
25f9c8
 }
25f9c8
 
25f9c8
+static void parse_pdeathsig(struct privctx *opts, const char *str)
25f9c8
+{
25f9c8
+	if (!strcmp(str, "keep")) {
25f9c8
+		if (prctl(PR_GET_PDEATHSIG, &opts->pdeathsig) != 0)
25f9c8
+			errx(SETPRIV_EXIT_PRIVERR,
25f9c8
+				 _("failed to get parent death signal"));
25f9c8
+	} else if (!strcmp(str, "clear")) {
25f9c8
+		opts->pdeathsig = -1;
25f9c8
+	} else if ((opts->pdeathsig = signame_to_signum(str)) < 0) {
25f9c8
+		errx(EXIT_FAILURE, _("unknown signal: %s"), str);
25f9c8
+	}
25f9c8
+}
25f9c8
+
25f9c8
 static void do_setresuid(const struct privctx *opts)
25f9c8
 {
25f9c8
 	uid_t ruid, euid, suid;
25f9c8
@@ -711,6 +748,7 @@ int main(int argc, char **argv)
25f9c8
 		LISTCAPS,
25f9c8
 		CAPBSET,
25f9c8
 		SECUREBITS,
25f9c8
+		PDEATHSIG,
25f9c8
 		SELINUX_LABEL,
25f9c8
 		APPARMOR_PROFILE
25f9c8
 	};
25f9c8
@@ -734,6 +772,7 @@ int main(int argc, char **argv)
25f9c8
 		{ "groups",           required_argument, NULL, GROUPS           },
25f9c8
 		{ "bounding-set",     required_argument, NULL, CAPBSET          },
25f9c8
 		{ "securebits",       required_argument, NULL, SECUREBITS       },
25f9c8
+		{ "pdeathsig",        required_argument, NULL, PDEATHSIG,       },
25f9c8
 		{ "selinux-label",    required_argument, NULL, SELINUX_LABEL    },
25f9c8
 		{ "apparmor-profile", required_argument, NULL, APPARMOR_PROFILE },
25f9c8
 		{ "help",             no_argument,       NULL, 'h'              },
25f9c8
@@ -844,6 +883,12 @@ int main(int argc, char **argv)
25f9c8
 				     _("duplicate --groups option"));
25f9c8
 			parse_groups(&opts, optarg);
25f9c8
 			break;
25f9c8
+		case PDEATHSIG:
25f9c8
+			if (opts.pdeathsig)
25f9c8
+				errx(EXIT_FAILURE,
25f9c8
+				     _("duplicate --keep-pdeathsig option"));
25f9c8
+			parse_pdeathsig(&opts, optarg);
25f9c8
+			break;
25f9c8
 		case LISTCAPS:
25f9c8
 			list_caps = 1;
25f9c8
 			break;
25f9c8
@@ -989,6 +1034,10 @@ int main(int argc, char **argv)
25f9c8
 		do_caps(CAP_TYPE_AMBIENT, opts.ambient_caps);
25f9c8
 	}
25f9c8
 
25f9c8
+	/* Clear or set parent death signal */
25f9c8
+	if (opts.pdeathsig && prctl(PR_SET_PDEATHSIG, opts.pdeathsig < 0 ? 0 : opts.pdeathsig) != 0)
25f9c8
+		err(SETPRIV_EXIT_PRIVERR, _("set parent death signal failed"));
25f9c8
+
25f9c8
 	execvp(argv[optind], argv + optind);
25f9c8
 	errexec(argv[optind]);
25f9c8
 }
25f9c8
-- 
25f9c8
2.31.1
25f9c8