SPECS/shadow/shadow-4.2.1-CVE-2018-7169.patch
4367ca14
 --- a/src/newgidmap.c	2018-07-31 05:56:46.642785135 +0530
 +++ b/src/newgidmap.c	2018-07-31 06:03:36.578802898 +0530
 @@ -46,32 +46,36 @@
   */
  const char *Prog;
  
 -static bool verify_range(struct passwd *pw, struct map_range *range)
 +static bool verify_range(struct passwd *pw, struct map_range *range, bool *allow_setgroups)
  {
  	/* An empty range is invalid */
  	if (range->count == 0)
  		return false;
  
 -	/* Test /etc/subgid */
 -	if (have_sub_gids(pw->pw_name, range->lower, range->count))
 +	/* Test /etc/subgid. If the mapping is valid then we allow setgroups. */
 +	if (have_sub_gids(pw->pw_name, range->lower, range->count)) {
 +		*allow_setgroups = true;
  		return true;
 +	}
  
  	/* Allow a process to map it's own gid */
 -	if ((range->count == 1) && (pw->pw_gid == range->lower))
 +	if ((range->count == 1) && (pw->pw_gid == range->lower)) {
 +		/* noop -- if setgroups is enabled already we won't disable it. */
  		return true;
 +	}
  
  	return false;
  }
  
  static void verify_ranges(struct passwd *pw, int ranges,
 -	struct map_range *mappings)
 +	struct map_range *mappings, bool *allow_setgroups)
  {
  	struct map_range *mapping;
  	int idx;
  
  	mapping = mappings;
  	for (idx = 0; idx < ranges; idx++, mapping++) {
 -		if (!verify_range(pw, mapping)) {
 +		if (!verify_range(pw, mapping, allow_setgroups)) {
  			fprintf(stderr, _( "%s: gid range [%lu-%lu) -> [%lu-%lu) not allowed\n"),
  				Prog,
  				mapping->upper,
 @@ -83,6 +87,70 @@ static void verify_ranges(struct passwd
  	}
  }
  
 +void write_setgroups(int proc_dir_fd, bool allow_setgroups)
 +{
 +	int setgroups_fd;
 +	char *policy, policy_buffer[4096];
 +
 +	/*
 +	 * Default is "deny", and any "allow" will out-rank a "deny". We don't
 +	 * forcefully write an "allow" here because the process we are writing
 +	 * mappings for may have already set themselves to "deny" (and "allow"
 +	 * is the default anyway). So allow_setgroups == true is a noop.
 +	 */
 +	policy = "deny\n";
 +	if (allow_setgroups)
 +		return;
 +
 +	setgroups_fd = openat(proc_dir_fd, "setgroups", O_RDWR|O_CLOEXEC);
 +	if (setgroups_fd < 0) {
 +		/*
 +		 * If it's an ENOENT then we are on too old a kernel for the setgroups
 +		 * code to exist. Emit a warning and bail on this.
 +		 */
 +		if (ENOENT == errno) {
 +			fprintf(stderr, _("%s: kernel doesn't support setgroups restrictions\n"), Prog);
 +			goto out;
 +		}
 +		fprintf(stderr, _("%s: couldn't open process setgroups: %s\n"),
 +			Prog,
 +			strerror(errno));
 +		exit(EXIT_FAILURE);
 +	}
 +
 +	/*
 +	 * Check whether the policy is already what we want. /proc/self/setgroups
 +	 * is write-once, so attempting to write after it's already written to will
 +	 * fail.
 +	 */
 +	if (read(setgroups_fd, policy_buffer, sizeof(policy_buffer)) < 0) {
 +		fprintf(stderr, _("%s: failed to read setgroups: %s\n"),
 +			Prog,
 +			strerror(errno));
 +		exit(EXIT_FAILURE);
 +	}
 +	if (!strncmp(policy_buffer, policy, strlen(policy)))
 +		goto out;
 +
 +	/* Write the policy. */
 +	if (lseek(setgroups_fd, 0, SEEK_SET) < 0) {
 +		fprintf(stderr, _("%s: failed to seek setgroups: %s\n"),
 +			Prog,
 +			strerror(errno));
 +		exit(EXIT_FAILURE);
 +	}
 +	if (dprintf(setgroups_fd, "%s", policy) < 0) {
 +		fprintf(stderr, _("%s: failed to setgroups %s policy: %s\n"),
 +			Prog,
 +			policy,
 +			strerror(errno));
 +		exit(EXIT_FAILURE);
 +	}
 +
 +out:
 +	close(setgroups_fd);
 +}
 +
  static void usage(void)
  {
  	fprintf(stderr, _("usage: %s <pid> <gid> <lowergid> <count> [ <gid> <lowergid> <count> ] ... \n"), Prog);
 @@ -103,6 +171,7 @@ int main(int argc, char **argv)
  	struct stat st;
  	struct passwd *pw;
  	int written;
 +	bool allow_setgroups = false;
  
  	Prog = Basename (argv[0]);
  
 @@ -174,8 +243,9 @@ int main(int argc, char **argv)
  	if (!mappings)
  		usage();
  
 -	verify_ranges(pw, ranges, mappings);
 +	verify_ranges(pw, ranges, mappings, &allow_setgroups);
  
 +	write_setgroups(proc_dir_fd, allow_setgroups);
  	write_mapping(proc_dir_fd, ranges, mappings, "gid_map");
  	sub_gid_close();