On Sat, 07 Jan 2017, Salvatore Bonaccorso wrote: > If you fix the vulnerability please also make sure to include the > CVE (Common Vulnerabilities & Exposures) id in your changelog entry.
If you want to fix this for jessie, you should start with the patch from the 15.08 branch and it should be easy to backport: https://github.com/SchedMD/slurm/commit/465c98ccff9f1e0018e6a0e6e86ee485ae480ae6 At least it was for me for the version in wheezy. I attach my backported patch for reference. But I don't really know how to test it... Cheers, -- Raphaël Hertzog ◈ Debian Developer Support Debian LTS: https://www.freexian.com/services/debian-lts.html Learn to master Debian: https://debian-handbook.info/get/
>From 465c98ccff9f1e0018e6a0e6e86ee485ae480ae6 Mon Sep 17 00:00:00 2001 From: Tim Wickberg <t...@schedmd.com> Date: Wed, 4 Jan 2017 13:58:01 -0700 Subject: [PATCH] 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. CVE-2016-10030. [hert...@debian.org: Patch grabbed in the slurm-15.08 branch which is closer to the very old version in wheezy - drop calls related to containerization (feature not available in wheezy) - adjust parameters to _get_grouplist() ] Origin: backport, https://github.com/SchedMD/slurm/commit/465c98ccff9f1e0018e6a0e6e86ee485ae480ae6 Bug-Debian: https://bugs.debian.org/850491 --- NEWS | 3 + src/slurmd/slurmd/req.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 4 deletions(-) --- a/src/slurmd/slurmd/req.c +++ b/src/slurmd/slurmd/req.c @@ -133,6 +133,7 @@ static bool _job_still_running(uint32_t static int _kill_all_active_steps(uint32_t jobid, int sig, bool batch); static int _step_limits_match(void *x, void *key); static int _terminate_all_steps(uint32_t jobid, bool batch); +static int _receive_fd(int socket); static void _rpc_launch_tasks(slurm_msg_t *); static void _rpc_abort_job(slurm_msg_t *); static void _rpc_batch_job(slurm_msg_t *); @@ -186,6 +187,7 @@ static void _add_job_running_prolog(uint static void _remove_job_running_prolog(uint32_t job_id); static int _compare_job_running_prolog(void *s0, void *s1); static void _wait_for_job_running_prolog(uint32_t job_id); +static int _open_as_other(char *path_name, batch_job_launch_msg_t *req); /* * List of threads waiting for jobs to complete @@ -1084,10 +1086,8 @@ _prolog_error(batch_job_launch_msg_t *re req->work_dir, err_name_ptr); else snprintf(path_name, MAXPATHLEN, "/%s", err_name_ptr); - - if ((fd = open(path_name, (O_CREAT|O_APPEND|O_WRONLY), 0644)) == -1) { - error("Unable to open %s: %s", path_name, - slurm_strerror(errno)); + if ((fd = _open_as_other(path_name, req)) == -1) { + error("Unable to open %s: Permission denied", path_name); return; } snprintf(err_name, sizeof(err_name), @@ -4355,3 +4355,139 @@ static void _wait_for_job_running_prolog slurm_mutex_unlock(&conf->prolog_running_lock); debug( "Finished wait for job %d's prolog to complete", job_id); } + +/* pass an open file descriptor back to the parent process */ +static void _send_back_fd(int socket, int fd) +{ + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + char buf[CMSG_SPACE(sizeof(fd))]; + memset(buf, '\0', sizeof(buf)); + + msg.msg_iov = NULL; + msg.msg_iovlen = 0; + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + + memmove(CMSG_DATA(cmsg), &fd, sizeof(fd)); + msg.msg_controllen = cmsg->cmsg_len; + + if (sendmsg(socket, &msg, 0) < 0) + error("%s: failed to send fd: %m", __func__); +} + +/* receive an open file descriptor from fork()'d child over unix socket */ +static int _receive_fd(int socket) +{ + struct msghdr msg = {0}; + struct cmsghdr *cmsg; + int fd; + msg.msg_iov = NULL; + msg.msg_iovlen = 0; + char c_buffer[256]; + msg.msg_control = c_buffer; + msg.msg_controllen = sizeof(c_buffer); + + if (recvmsg(socket, &msg, 0) < 0) { + error("%s: failed to receive fd: %m", __func__); + return -1; + } + + cmsg = CMSG_FIRSTHDR(&msg); + memmove(&fd, CMSG_DATA(cmsg), sizeof(fd)); + return 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; + int ngroups = 16; + gid_t *groups; + int pipe[2]; + int fd = -1, rc = 0; + + if ((rc = _get_grouplist(req->uid, req->gid, &ngroups, &groups)) < 0) { + error("%s: getgrouplist(%u): %m", __func__, req->uid); + return rc; + } + + /* 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__); + xfree(groups); + return -1; + } + + child = fork(); + if (child == -1) { + error("%s: fork failure", __func__); + xfree(groups); + close(pipe[0]); + close(pipe[1]); + return -1; + } else if (child > 0) { + close(pipe[0]); + (void) waitpid(child, &rc, 0); + xfree(groups); + if (WIFEXITED(rc) && (WEXITSTATUS(rc) == 0)) + fd = _receive_fd(pipe[1]); + close(pipe[1]); + return fd; + } + + /* child process below here */ + + close(pipe[1]); + + /* 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(ngroups, groups) < 0) { + error("%s: uid: %u setgroups failed: %m", __func__, req->uid); + exit(errno); + } + xfree(groups); + + 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); +}