minijail: Add ability to keep supplementary gids.

Adds the ability to keep supplementary group ids. If an outer process sets a
saved uid, this allows changing to the saved uid in an inner, minijailed process.
Without this, the inner jail would try to clear supplementary groups
(setgroups(0, NULL)), which may not be allowed due to missing caps.

Bug: 33838120
TEST=Tested using the authpolicy project in Chrome OS

Change-Id: I9e98332324753922a4ac881b46233258067eaeae
diff --git a/libminijail.c b/libminijail.c
index 4e46da5..7f41b33 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -123,8 +123,9 @@
 	struct {
 		int uid : 1;
 		int gid : 1;
-		int usergroups : 1;
-		int suppl_gids : 1;
+		int inherit_suppl_gids : 1;
+		int set_suppl_gids : 1;
+		int keep_suppl_gids : 1;
 		int use_caps : 1;
 		int capbset_drop : 1;
 		int vfs : 1;
@@ -250,14 +251,14 @@
 {
 	size_t i;
 
-	if (j->flags.usergroups)
-		die("cannot inherit *and* set supplementary groups");
+	if (j->flags.inherit_suppl_gids || j->flags.keep_suppl_gids)
+		die("cannot inherit *and* set or keep supplementary groups");
 
 	if (size == 0) {
 		/* Clear supplementary groups. */
 		j->suppl_gid_list = NULL;
 		j->suppl_gid_count = 0;
-		j->flags.suppl_gids = 1;
+		j->flags.set_suppl_gids = 1;
 		return;
 	}
 
@@ -270,7 +271,11 @@
 		j->suppl_gid_list[i] = list[i];
 	}
 	j->suppl_gid_count = size;
-	j->flags.suppl_gids = 1;
+	j->flags.set_suppl_gids = 1;
+}
+
+void API minijail_keep_supplementary_gids(struct minijail *j) {
+	j->flags.keep_suppl_gids = 1;
 }
 
 int API minijail_change_user(struct minijail *j, const char *user)
@@ -514,7 +519,7 @@
 
 void API minijail_inherit_usergroups(struct minijail *j)
 {
-	j->flags.usergroups = 1;
+	j->flags.inherit_suppl_gids = 1;
 }
 
 void API minijail_run_as_init(struct minijail *j)
@@ -1327,22 +1332,22 @@
 
 static void drop_ugid(const struct minijail *j)
 {
-	if (j->flags.usergroups && j->flags.suppl_gids) {
-		die("tried to inherit *and* set supplementary groups;"
-		    " can only do one");
+	if (j->flags.inherit_suppl_gids + j->flags.keep_suppl_gids +
+	    j->flags.set_suppl_gids > 1) {
+		die("can only either inherit, keep or set supplementary groups;"
+		    " tried to do two or more");
 	}
 
-	if (j->flags.usergroups) {
+	if (j->flags.inherit_suppl_gids) {
 		if (initgroups(j->user, j->usergid))
 			pdie("initgroups(%s, %d) failed", j->user, j->usergid);
-	} else if (j->flags.suppl_gids) {
-		if (setgroups(j->suppl_gid_count, j->suppl_gid_list)) {
+	} else if (j->flags.set_suppl_gids) {
+		if (setgroups(j->suppl_gid_count, j->suppl_gid_list))
 			pdie("setgroups(suppl_gids) failed");
-		}
-	} else {
+	} else if (!j->flags.keep_suppl_gids) {
 		/*
 		 * Only attempt to clear supplementary groups if we are changing
-		 * users.
+		 * users or groups.
 		 */
 		if ((j->flags.uid || j->flags.gid) && setgroups(0, NULL))
 			pdie("setgroups(0, NULL) failed");
@@ -1564,7 +1569,7 @@
 		die("tried to enter a pid-namespaced jail;"
 		    " try minijail_run()?");
 
-	if (j->flags.usergroups && !j->user)
+	if (j->flags.inherit_suppl_gids && !j->user)
 		die("usergroup inheritance without username");
 
 	/*
diff --git a/libminijail.h b/libminijail.h
index 63d1c11..58ce847 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -43,6 +43,7 @@
 /* Copies |list|. */
 void minijail_set_supplementary_gids(struct minijail *j, size_t size,
 				     const gid_t *list);
+void minijail_keep_supplementary_gids(struct minijail *j);
 /* Stores user to change to and copies |user| for internal consistency. */
 int minijail_change_user(struct minijail *j, const char *user);
 /* Does not take ownership of |group|. */
diff --git a/minijail0.1 b/minijail0.1
index 41e27ff..03cd391 100644
--- a/minijail0.1
+++ b/minijail0.1
@@ -34,15 +34,15 @@
 \fB-f <file>\fR
 Write the pid of the jailed process to \fIfile\fR.
 .TP
+\fB-g <group>\fR
+Change groups to \fIgroup\fR, which may be either a group name or a numeric
+group ID.
+.TP
 \fB-G\fR
 Inherit all the supplementary groups of the user specified with \fB-u\fR. It
 is an error to use this option without having specified a \fBuser name\fR to
 \fB-u\fR.
 .TP
-\fB-g <group>\fR
-Change groups to \fIgroup\fR, which may be either a group name or a numeric
-group ID.
-.TP
 \fB-h\fR
 Print a help message.
 .TP
@@ -135,6 +135,9 @@
 \fB-V <file>\fR
 Enter the VFS namespace specified by \fIfile\fR.
 .TP
+\fB-y\fR
+Keep the current user's supplementary groups.
+.TP
 \fB-Y\fR
 Synchronize seccomp filters across thread group.
 .SH IMPLEMENTATION
diff --git a/minijail0.c b/minijail0.c
index e15c884..ed0f716 100644
--- a/minijail0.c
+++ b/minijail0.c
@@ -110,7 +110,7 @@
 {
 	size_t i;
 	/* clang-format off */
-	printf("Usage: %s [-GhHiIKlLnNprstUvY]\n"
+	printf("Usage: %s [-GhHiIKlLnNprstUvyY]\n"
 	       "  [-a <table>]\n"
 	       "  [-b <src>,<dest>[,<writeable>]] [-k <src>,<dest>,<type>[,<flags>][,<data>]]\n"
 	       "  [-c <caps>] [-C <dir>] [-P <dir>] [-e[file]] [-f <file>] [-g <group>]\n"
@@ -132,6 +132,9 @@
 	       "  -f <file>:  Write the pid of the jailed process to <file>.\n"
 	       "  -g <group>: Change gid to <group>.\n"
 	       "  -G:         Inherit supplementary groups from uid.\n"
+	       "              Not compatible with -y.\n"
+	       "  -y:         Keep uid's supplementary groups.\n"
+	       "              Not compatible with -G.\n"
 	       "  -h:         Help (this message).\n"
 	       "  -H:         Seccomp filter help message.\n"
 	       "  -i:         Exit immediately after fork (do not act as init).\n"
@@ -191,6 +194,7 @@
 	int binding = 0;
 	int pivot_root = 0, chroot = 0;
 	int mount_ns = 0, skip_remount = 0;
+	int inherit_suppl_gids = 0, keep_suppl_gids = 0;
 	const size_t path_max = 4096;
 	char *map;
 	const char *filter_path;
@@ -198,7 +202,7 @@
 		return 1;
 
 	const char *optstring =
-	    "u:g:sS:c:C:P:b:V:f:m::M::k:a:e::T:vrGhHinNplLtIUKY";
+	    "u:g:sS:c:C:P:b:V:f:m::M::k:a:e::T:vrGhHinNplLtIUKyY";
 	while ((opt = getopt(argc, argv, optstring)) != -1) {
 		switch (opt) {
 		case 'u':
@@ -294,7 +298,22 @@
 			minijail_remount_proc_readonly(j);
 			break;
 		case 'G':
+			if (keep_suppl_gids) {
+				fprintf(stderr,
+					"-y and -G are not compatible.\n");
+				exit(1);
+			}
 			minijail_inherit_usergroups(j);
+			inherit_suppl_gids = 1;
+			break;
+		case 'y':
+			if (inherit_suppl_gids) {
+				fprintf(stderr,
+					"-y and -G are not compatible.\n");
+				exit(1);
+			}
+			minijail_keep_supplementary_gids(j);
+			keep_suppl_gids = 1;
 			break;
 		case 'N':
 			minijail_namespace_cgroups(j);