CVE-2016-10030 (Other, published 2017-01-05T11:59Z)
Description: value: The _prolog_error function in slurmd/req.c in Slurm before 15.08.13, 16.x before 16.05.7, and 17.x before 17.02.0-pre4 has a vulnerability in how the slurmd daemon informs users of a Prolog failure on a compute node. That vulnerability could allow a user to assume control of an arbitrary file on the system. Any exploitation of this is dependent on the user being able to cause or anticipate the failure (non-zero return code) of a Prolog script that their job would run on. This issue affects all Slurm versions from 0.6.0 (September 2005) to present. Workarounds to prevent exploitation of this are to either disable your Prolog script, or modify it such that it always returns 0 ("success") and adjust it to set the node as down using scontrol instead of relying on the slurmd to handle that automatically. If you do not have a Prolog set you are unaffected by this issue.
Severity: HIGH (CVSS v3 base score: 8.1)
Weakness: CWE-284 \u2014 Improper Access Control
The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
Repository: https://github.com/SchedMD/slurm
Files changed:
NEWS
src/slurmd/slurmd/req.c
Commit message: Fix security issue in _prolog_error().

Fix security issue caused by insecure file path handling triggered by
the failure of a Prolog script. To exploit this a user needs to
anticipate or cause the Prolog to fail for their job.

(This commit is slightly different from the fix to the 15.08 branch.)

CVE-2016-10030.


Vulnerable code:

	if ((fd = open(path_name, (O_CREAT|O_APPEND|O_WRONLY), 0644)) == -1) {
		error("Unable to open %s: %s", path_name,
		      slurm_strerror(errno));

Fixed code:
 -- Fix security issue caused by insecure file path handling triggered by the
    failure of a Prolog script. To exploit this a user needs to anticipate or
    cause the Prolog to fail for their job. CVE-2016-10030.
static int  _receive_fd(int socket);
static void _send_back_fd(int socket, int fd);
/*
 * Open file based upon permissions of a different user
 * IN path_name - name of file to open
 * IN uid - User ID to use for file access check
 * IN gid - Group ID to use for file access check
 * RET -1 on error, file descriptor otherwise
 */
static int _open_as_other(char *path_name, batch_job_launch_msg_t *req)
{
	pid_t child;
	gids_t *gids;
	int pipe[2];
	int fd = -1, rc = 0;

	if (!(gids = _gids_cache_lookup(req->user_name, req->gid))) {
		error("%s: gids_cache_lookup for %s failed",
		      __func__, req->user_name);
		return -1;
	}

	if ((rc = container_g_create(req->job_id))) {
		error("%s: container_g_create(%u): %m", __func__, req->job_id);
		_dealloc_gids(gids);
		return -1;
	}

	/* child process will setuid to the user, register the process
	 * with the container, and open the file for us. */
	if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pipe) != 0) {
		error("%s: Failed to open pipe: %m", __func__);
		_dealloc_gids(gids);
		return -1;
	}

	child = fork();
	if (child == -1) {
		error("%s: fork failure", __func__);
		_dealloc_gids(gids);
		close(pipe[0]);
		close(pipe[1]);
		return -1;
	} else if (child > 0) {
		close(pipe[0]);
		(void) waitpid(child, &rc, 0);
		_dealloc_gids(gids);
		if (WIFEXITED(rc) && (WEXITSTATUS(rc) == 0))
			fd = _receive_fd(pipe[1]);
		close(pipe[1]);
		return fd;
	}

	/* child process below here */

	close(pipe[1]);

	/* container_g_add_pid needs to be called in the
	 * forked process part of the fork to avoid a race
	 * condition where if this process makes a file or
	 * detacts itself from a child before we add the pid
	 * to the container in the parent of the fork. */
	if (container_g_add_pid(req->job_id, getpid(), req->uid)) {
		error("%s container_g_add_pid(%u): %m", __func__, req->job_id);
		exit(SLURM_ERROR);
	}

	/* The child actually performs the I/O and exits with
	 * a return code, do not return! */

	/*********************************************************************\
	 * NOTE: It would be best to do an exec() immediately after the fork()
	 * in order to help prevent a possible deadlock in the child process
	 * due to locks being set at the time of the fork and being freed by
	 * the parent process, but not freed by the child process. Performing
	 * the work inline is done for simplicity. Note that the logging
	 * performed by error() should be safe due to the use of
	 * atfork_install_handlers() as defined in src/common/log.c.
	 * Change the code below with caution.
	\*********************************************************************/

	if (setgroups(gids->ngids, gids->gids) < 0) {
		error("%s: uid: %u setgroups failed: %m", __func__, req->uid);
		exit(errno);
	}
	_dealloc_gids(gids);

	if (setgid(req->gid) < 0) {
		error("%s: uid:%u setgid(%u): %m", __func__, req->uid,req->gid);
		exit(errno);
	}
	if (setuid(req->uid) < 0) {
		error("%s: getuid(%u): %m", __func__, req->uid);
		exit(errno);
	}

	fd = open(path_name, (O_CREAT|O_APPEND|O_WRONLY), 0644);
	if (fd == -1) {
		error("%s: uid:%u can't open `%s`: %m",
		      __func__, req->uid, path_name);
		exit(errno);
	}
	_send_back_fd(pipe[0], fd);
	close(fd);
	exit(SLURM_SUCCESS);
}

	if ((fd = _open_as_other(path_name, req)) == -1) {
		error("Unable to open %s: Permission denied", path_name);