Commit message: ExecHelperMain: fix privilege lowering code
CVE: CVE-2018-12615
CWE: CWE-284
Vulnerability description: An issue was discovered in switchGroup() in agent/ExecHelper/ExecHelperMain.cpp in Phusion Passenger before 5.3.2. The set of groups (gidset) is not set correctly, leaving it up to randomness (i.e., uninitialized memory) which supplementary groups are actually being set while lowering privileges.

Project: passenger
File: ExecHelperMain.cpp

parseOptions(int argc, const char *argv[], Options &options) {
	OptionParser p(usage);
	int i = 2;

	while (i < argc) {
		if (parseOption(argc, argv, i, options)) {
			continue;
		} else if (p.isFlag(argv[i], 'h', "--help")) {
			usage();
			exit(0);
		} else if (*argv[i] == '-') {
			fprintf(stderr, "ERROR: unrecognized argument %s. Please type "
				"'%s exec-helper --help' for usage.\n", argv[i], argv[0]);
			exit(1);
		} else {
			options.programArgStart = i;
			return true;
		}
	}

	return true;
}

Project: passenger
File: ExecHelperMain.cpp

	Options()
		: programArgStart(2)
		{ }

Project: passenger
File: ExecHelperMain.cpp

describeCommand(int argc, const char *argv[], const Options &options) {
	string result = "'";
	result.append(argv[options.programArgStart]);
	result.append("'");

	if (argc > options.programArgStart + 1) {
		result.append(" (with params '");

		int i = options.programArgStart + 1;
		while (i < argc) {
			if (i != options.programArgStart + 1) {
				result.append(" ");
			}
			result.append(argv[i]);
			i++;
		}

		result.append("')");
	}

	return result;
}

Project: passenger
File: ExecHelperMain.cpp

parseOption(int argc, const char *argv[], int &i, Options &options) {
	OptionParser p(usage);

	if (p.isValueFlag(argc, i, argv[i], '\0', "--user")) {
		options.user = argv[i + 1];
		i += 2;
	} else {
		return false;
	}
	return true;
}

Project: passenger
File: ExecHelperMain.cpp

switchGroup(uid_t uid, const struct passwd *userInfo, gid_t gid) {
	if (userInfo != NULL) {
		bool setgroupsCalled = false;

		#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
			#ifdef __APPLE__
				int groups[1024];
				int ngroups = sizeof(groups) / sizeof(int);
			#else
				gid_t groups[1024];
				int ngroups = sizeof(groups) / sizeof(gid_t);
			#endif
			boost::scoped_array<gid_t> gidset;

			int ret = getgrouplist(userInfo->pw_name, gid,
				groups, &ngroups);
			if (ret == -1) {
				int e = errno;
				fprintf(stderr, "ERROR: getgrouplist(%s, %d) failed: %s (errno=%d)\n",
					userInfo->pw_name, (int) gid, strerror(e), e);
				exit(1);
			}

			if (ngroups <= NGROUPS_MAX) {
				setgroupsCalled = true;
				gidset.reset(new gid_t[ngroups]);
				for (int i = 0; i < ngroups; i++) {
					gidset[i] = groups[i];
				}
				if (setgroups(ngroups, gidset.get()) == -1) {
					int e = errno;
					fprintf(stderr, "ERROR: setgroups(%d, ...) failed: %s (errno=%d)\n",
						ngroups, strerror(e), e);
					exit(1);
				}
			}
		#endif

		if (!setgroupsCalled && initgroups(userInfo->pw_name, gid) == -1) {
			int e = errno;
			fprintf(stderr, "ERROR: initgroups(%s, %d) failed: %s (errno=%d)\n",
				userInfo->pw_name, (int) gid, strerror(e), e);
			exit(1);
		}
	}

	if (setgid(gid) == -1) {
		int e = errno;
		fprintf(stderr, "ERROR: setgid(%d) failed: %s (errno=%d)\n",
			(int) gid, strerror(e), e);
		exit(1);
	}
}

Project: passenger
File: ExecHelperMain.cpp

usage() {
	// ....|---------------Keep output within standard terminal width (80 chars)------------|
	printf("Usage: " AGENT_EXE " exec-helper [OPTIONS...] <PROGRAM> [ARGS...]\n");
	printf("Executes the given program under a specific environment.\n");
	printf("\n");
	printf("Options:\n");
	printf("  --user <USER>   Execute as the given user. The GID will be set to the\n");
	printf("                  user's primary group. Supplementary groups will also\n");
	printf("                  be set.\n");
	printf("  --help          Show this help message.\n");
}

Project: passenger
File: ExecHelperMain.cpp

switchUser(uid_t uid, const struct passwd *userInfo) {
	if (setuid(uid) == -1) {
		int e = errno;
		fprintf(stderr, "setuid(%d) failed: %s (errno=%d)\n",
			(int) uid, strerror(e), e);
		exit(1);
	}
	if (userInfo != NULL) {
		setenv("USER", userInfo->pw_name, 1);
		setenv("LOGNAME", userInfo->pw_name, 1);
		setenv("SHELL", userInfo->pw_shell, 1);
		setenv("HOME", userInfo->pw_dir, 1);
	} else {
		unsetenv("USER");
		unsetenv("LOGNAME");
		unsetenv("SHELL");
		unsetenv("HOME");
	}
}

Project: passenger
File: ExecHelperMain.cpp

execHelperMain(int argc, char *argv[]) {
	if (argc < 3) {
		usage();
		exit(1);
	}

	Options options;
	if (!parseOptions(argc, (const char **) argv, options)) {
		fprintf(stderr, "Error parsing arguments.\n");
		usage();
		exit(1);
	}

	resetSignalHandlersAndMask();
	disableMallocDebugging();

	if (!options.user.empty()) {
		struct passwd *userInfo;
		uid_t uid;
		gid_t gid;

		lookupUserGroup(options.user, &uid, &userInfo, &gid);
		switchGroup(uid, userInfo, gid);
		switchUser(uid, userInfo);
	}

	execvp(argv[options.programArgStart],
		(char * const *) &argv[options.programArgStart]);
	int e = errno;
	fprintf(stderr, "ERROR: unable to execute %s: %s (errno=%d)\n",
		describeCommand(argc, (const char **) argv, options).c_str(),
		strerror(e),
		e);
	return 1;
}

Project: passenger
File: ExecHelperMain.cpp

execHelperMain(int argc, char *argv[]) {
	return Passenger::ExecHelper::execHelperMain(argc, argv);
}

Project: passenger
File: ExecHelperMain.cpp

reportGetpwuidError(const string &user, int e) {
	if (e == 0) {
		fprintf(stderr,
			"ERROR: Cannot lookup up system user database entry for user '%s':"
			" user does not exist\n", user.c_str());
	} else {
		fprintf(stderr,
			"ERROR: Cannot lookup up system user database entry for user '%s':"
			" %s (errno=%d)\n",
			user.c_str(), strerror(e), e);
	}
}

Project: passenger
File: ExecHelperMain.cpp

lookupUserGroup(const string &user, uid_t *uid, struct passwd **userInfo, gid_t *gid) {
	errno = 0;
	*userInfo = getpwnam(user.c_str());
	if (*userInfo == NULL) {
		if (looksLikePositiveNumber(user)) {
			int e = errno;
			fprintf(stderr,
				"Warning: error looking up system user database"
				" entry for user '%s': %s (errno=%d)\n",
				user.c_str(), strerror(e), e);
			*uid = (uid_t) atoi(user.c_str());
			*userInfo = getpwuid(*uid);
			if (*userInfo == NULL) {
				reportGetpwuidError(user, errno);
				exit(1);
			} else {
				*gid = (*userInfo)->pw_gid;
			}
		} else {
			reportGetpwuidError(user, errno);
			exit(1);
		}
	} else {
		*uid = (*userInfo)->pw_uid;
		*gid = (*userInfo)->pw_gid;
	}
}