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);
+}

Reply via email to