From 2aad72c3d2ac612ecbb66828ac6ed5ab51eff5f3 Mon Sep 17 00:00:00 2001 From: David Beer Date: Mon, 11 Nov 2013 11:55:58 -0700 Subject: [PATCH] Fix CVE 2013-4495. Note: this patch has been verified as fixing this security hole but has not received other regression testing. Could not cherry-pick as 2.5 and 4.1 are very different. --- src/server/svr_mail.c | 265 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 170 insertions(+), 95 deletions(-) diff --git a/src/server/svr_mail.c b/src/server/svr_mail.c index b269e82..52f2f1f 100644 --- a/src/server/svr_mail.c +++ b/src/server/svr_mail.c @@ -89,6 +89,7 @@ #include #include #include +#include #include "list_link.h" #include "attribute.h" #include "server_limits.h" @@ -136,6 +137,77 @@ void free_mail_info( +void add_body_info( + + char *bodyfmtbuf /* I */, + mail_info *mi /* I */) + + { + char *bodyfmt = NULL; + bodyfmt = strcpy(bodyfmtbuf, "PBS Job Id: %i\n" + "Job Name: %j\n"); + if (mi->exec_host != NULL) + { + strcat(bodyfmt, "Exec host: %h\n"); + } + + strcat(bodyfmt, "%m\n"); + + if (mi->text != NULL) + { + strcat(bodyfmt, "%d\n"); + } + } + + +/* + * write_email() + * + * In emailing, the mail body is written to a pipe connected to + * standard input for sendmail. This function supplies the body + * of the message. + * + */ +void write_email( + + FILE *outmail_input, + mail_info *mi) + + { + char *bodyfmt = NULL; + char *subjectfmt = NULL; + + /* Pipe in mail headers: To: and Subject: */ + fprintf(outmail_input, "To: %s\n", mi->mailto); + + /* mail subject line formating statement */ + get_svr_attr_str(SRV_ATR_MailSubjectFmt, (char **)&subjectfmt); + if (subjectfmt == NULL) + { + subjectfmt = "PBS JOB %i"; + } + + fprintf(outmail_input, "Subject: "); + svr_format_job(outmail_input, mi, subjectfmt); + fprintf(outmail_input, "\n"); + + /* Set "Precedence: bulk" to avoid vacation messages, etc */ + fprintf(outmail_input, "Precedence: bulk\n\n"); + + /* mail body formating statement */ + get_svr_attr_str(SRV_ATR_MailBodyFmt, &bodyfmt); + if (bodyfmt == NULL) + { + char bodyfmtbuf[MAXLINE]; + add_body_info(bodyfmtbuf, mi); + bodyfmt = bodyfmtbuf; + } + + /* Now pipe in the email body */ + svr_format_job(outmail_input, mi, bodyfmt); + + } /* write_email() */ + void *send_the_mail( @@ -143,15 +215,19 @@ void *send_the_mail( void *vp) { - mail_info *mi = (mail_info *)vp; - - int i; - char *mailfrom = NULL; - char *subjectfmt = NULL; - char *bodyfmt = NULL; - char *cmdbuf = NULL; - char bodyfmtbuf[MAXLINE]; - FILE *outmail; + mail_info *mi = (mail_info *)vp; + + int status = 0; + int numargs = 0; + int pipes[2]; + int counter; + pid_t pid; + char *mailptr; + char *mailfrom = NULL; + char tmpBuf[LOG_BUF_SIZE]; + // We call sendmail with cmd_name + 2 arguments + # of mailto addresses + 1 for null + char *sendmail_args[100]; + FILE *stream; /* Who is mail from, if SRV_ATR_mailfrom not set use default */ get_svr_attr_str(SRV_ATR_mailfrom, &mailfrom); @@ -173,124 +249,123 @@ void *send_the_mail( mailfrom = PBS_DEFAULT_MAIL; } - /* mail subject line formating statement */ - get_svr_attr_str(SRV_ATR_MailSubjectFmt, &subjectfmt); - if (subjectfmt == NULL) - { - subjectfmt = "PBS JOB %i"; - } + sendmail_args[numargs++] = (char *)SENDMAIL_CMD; + sendmail_args[numargs++] = (char *)"-f"; + sendmail_args[numargs++] = (char *)mailfrom; - /* mail body formating statement */ - get_svr_attr_str(SRV_ATR_MailBodyFmt, &bodyfmt); - if (bodyfmt == NULL) + /* Add the e-mail addresses to the command line */ + mailptr = strdup(mi->mailto); + sendmail_args[numargs++] = mailptr; + for (counter=0; counter < (int)strlen(mailptr); counter++) { - bodyfmt = strcpy(bodyfmtbuf, "PBS Job Id: %i\n" - "Job Name: %j\n"); - if (mi->exec_host != NULL) + if (mailptr[counter] == ',') { - strcat(bodyfmt, "Exec host: %h\n"); - } - - strcat(bodyfmt, "%m\n"); - - if (mi->text != NULL) - { - strcat(bodyfmt, "%d\n"); + mailptr[counter] = '\0'; + sendmail_args[numargs++] = mailptr + counter + 1; + if (numargs >= 99) + break; } } - /* setup sendmail command line with -f from_whom */ - i = strlen(SENDMAIL_CMD) + strlen(mailfrom) + strlen(mi->mailto) + 6; - - if ((cmdbuf = calloc(1, i + 1)) == NULL) + sendmail_args[numargs] = NULL; + + /* Create a pipe to talk to the sendmail process we are about to fork */ + if (pipe(pipes) == -1) { - char tmpBuf[LOG_BUF_SIZE]; - - snprintf(tmpBuf,sizeof(tmpBuf), - "Unable to popen() command '%s' for writing: '%s' (error %d)\n", - SENDMAIL_CMD, - strerror(errno), - errno); + snprintf(tmpBuf, sizeof(tmpBuf), "Unable to pipes for sending e-mail\n"); log_event(PBSEVENT_ERROR | PBSEVENT_ADMIN | PBSEVENT_JOB, PBS_EVENTCLASS_JOB, mi->jobid, tmpBuf); - - free_mail_info(mi); + free_mail_info(mi); + free(mailptr); return(NULL); } - sprintf(cmdbuf, "%s -f %s %s", - SENDMAIL_CMD, - mailfrom, - mi->mailto); - - outmail = popen(cmdbuf, "w"); - - if (outmail == NULL) + if ((pid=fork()) == -1) { - char tmpBuf[LOG_BUF_SIZE]; - - snprintf(tmpBuf,sizeof(tmpBuf), - "Unable to popen() command '%s' for writing: '%s' (error %d)\n", - cmdbuf, - strerror(errno), - errno); + snprintf(tmpBuf, sizeof(tmpBuf), "Unable to fork for sending e-mail\n"); log_event(PBSEVENT_ERROR | PBSEVENT_ADMIN | PBSEVENT_JOB, PBS_EVENTCLASS_JOB, mi->jobid, tmpBuf); free_mail_info(mi); - free(cmdbuf); - + free(mailptr); + close(pipes[0]); + close(pipes[1]); return(NULL); } + else if (pid == 0) + { + /* CHILD */ - /* Pipe in mail headers: To: and Subject: */ - fprintf(outmail, "To: %s\n", mi->mailto); + /* Make stdin the read end of the pipe */ + dup2(pipes[0], 0); - fprintf(outmail, "Subject: "); - svr_format_job(outmail, mi, subjectfmt); - fprintf(outmail, "\n"); + /* Close the rest of the open file descriptors */ + int numfds = sysconf(_SC_OPEN_MAX); + while (--numfds > 0) + close(numfds); - /* Set "Precedence: bulk" to avoid vacation messages, etc */ - fprintf(outmail, "Precedence: bulk\n\n"); + execv(SENDMAIL_CMD, sendmail_args); + /* This never returns, but if the execv fails the child should exit */ + exit(1); + } + else + { + /* This is the parent */ - /* Now pipe in the email body */ - svr_format_job(outmail, mi, bodyfmt); + /* Close the read end of the pipe */ + close(pipes[0]); - errno = 0; - if ((i = pclose(outmail)) != 0) - { - char tmpBuf[LOG_BUF_SIZE]; + /* Write the body to the pipe */ + stream = fdopen(pipes[1], "w"); + write_email(stream, mi); - snprintf(tmpBuf,sizeof(tmpBuf), - "Email '%c' to %s failed: Child process '%s' %s %d (errno %d:%s)\n", - mi->mail_point, - mi->mailto, - cmdbuf, - ((WIFEXITED(i)) ? ("returned") : ((WIFSIGNALED(i)) ? ("killed by signal") : ("croaked"))), - ((WIFEXITED(i)) ? (WEXITSTATUS(i)) : ((WIFSIGNALED(i)) ? (WTERMSIG(i)) : (i))), - errno, - strerror(errno)); - log_event(PBSEVENT_ERROR | PBSEVENT_ADMIN | PBSEVENT_JOB, - PBS_EVENTCLASS_JOB, - mi->jobid, - tmpBuf); - } - else if (LOGLEVEL >= 4) - { - log_event(PBSEVENT_ERROR | PBSEVENT_ADMIN | PBSEVENT_JOB, - PBS_EVENTCLASS_JOB, - mi->jobid, - "Email sent successfully\n"); - } + fflush(stream); + + /* Close and wait for the command to finish */ + if (fclose(stream) != 0) + { + snprintf(tmpBuf,sizeof(tmpBuf), + "Piping mail body to sendmail closed: errno %d:%s\n", + errno, strerror(errno)); + + log_event(PBSEVENT_ERROR | PBSEVENT_ADMIN | PBSEVENT_JOB, + PBS_EVENTCLASS_JOB, + mi->jobid, + tmpBuf); + } + + // we aren't going to block in order to find out whether or not sendmail worked + if ((waitpid(pid, &status, WNOHANG) != 0) && + (status != 0)) + { + snprintf(tmpBuf,sizeof(tmpBuf), + "Sendmail command returned %d. Mail may not have been sent\n", + status); + + log_event(PBSEVENT_ERROR | PBSEVENT_ADMIN | PBSEVENT_JOB, + PBS_EVENTCLASS_JOB, + mi->jobid, + tmpBuf); + } - free_mail_info(mi); - free(cmdbuf); + // don't leave zombies + while (waitpid(-1, &status, WNOHANG) != 0) + { + // zombie reaped, NO-OP + } + + free_mail_info(mi); + free(mailptr); + return(NULL); + } + /* NOT REACHED */ + return(NULL); } /* END send_the_mail() */ -- 1.8.3.2