X-Git-Url: https://git.hcoop.net/hcoop/debian/exim4.git/blobdiff_plain/798bcb8a094a3a7399d04d760c882f4f25922f41..0c0c20aac64e1ef4037c144efec9a064add0d055:/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch diff --git a/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch new file mode 100644 index 0000000..3e73ae1 --- /dev/null +++ b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch @@ -0,0 +1,303 @@ +From c166890023f56388cb3482cff3def04171a488c4 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Thu, 25 Mar 2021 22:48:09 +0100 +Subject: [PATCH 26/29] CVE-2020-28014, CVE-2021-27216: Arbitrary PID file + creation, clobbering, and deletion + +Arbitrary PID file creation, clobbering, and deletion. +Patch provided by Qualys. + +(cherry picked from commit 974f32939a922512b27d9f0a8a1cb5dec60e7d37) +(cherry picked from commit 43c6f0b83200b7082353c50187ef75de3704580a) +--- + doc/ChangeLog | 5 + + src/daemon.c | 212 ++++++++++++++++++++++++++++++++++++++---- + src/exim.c | 12 ++- + test/stderr/0433 | 24 +++++ + 4 files changed, 232 insertions(+), 21 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -10,13 +10,18 @@ QS/02 PID file creation/deletion: only p + runtime user. + + QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of + the Exim runtime user are allowed to create files. + ++QS/01 Creation of (database) files in $spool_dir: only uid=0 or the uid of ++ the Exim runtime user are allowed to create files. + + HS/01 Handle trailing backslash gracefully. (CVE-2019-15846) + ++QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim ++ runtime user. ++ + + Since version 4.92 + ------------------ + + JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -886,10 +886,198 @@ while ((pid = waitpid(-1, &status, WNOHA + } + } + } + + ++static void ++set_pid_file_path(void) ++{ ++if (override_pid_file_path) ++ pid_file_path = override_pid_file_path; ++ ++if (!*pid_file_path) ++ pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); ++ ++if (pid_file_path[0] != '/') ++ log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path); ++} ++ ++ ++enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE }; ++ ++/* Do various pid file operations as safe as possible. Ideally we'd just ++drop the privileges for creation of the pid file and not care at all about removal of ++the file. FIXME. ++Returns: true on success, false + errno==EACCES otherwise ++*/ ++static BOOL ++operate_on_pid_file(const enum pid_op operation, const pid_t pid) ++{ ++char pid_line[sizeof(int) * 3 + 2]; ++const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid); ++BOOL lines_match = FALSE; ++ ++char * path = NULL; ++char * base = NULL; ++char * dir = NULL; ++ ++const int dir_flags = O_RDONLY | O_NONBLOCK; ++const int base_flags = O_NOFOLLOW | O_NONBLOCK; ++const mode_t base_mode = 0644; ++struct stat sb; ++ ++int cwd_fd = -1; ++int dir_fd = -1; ++int base_fd = -1; ++ ++BOOL success = FALSE; ++errno = EACCES; ++ ++set_pid_file_path(); ++if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup; ++if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup; ++ ++path = CS string_copy(pid_file_path); ++if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */ ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path); ++ ++dir = (base != path) ? path : "/"; ++*base++ = '\0'; ++ ++if (!dir || !*dir || *dir != '/') goto cleanup; ++if (!base || !*base || strchr(base, '/') != NULL) goto cleanup; ++ ++cwd_fd = open(".", dir_flags); ++if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; ++dir_fd = open(dir, dir_flags); ++if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; ++ ++/* emulate openat */ ++if (fchdir(dir_fd) != 0) goto cleanup; ++base_fd = open(base, O_RDONLY | base_flags); ++if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ ++if (base_fd >= 0) ++ { ++ char line[sizeof(pid_line)]; ++ ssize_t len = -1; ++ ++ if (fstat(base_fd, &sb) != 0 || !S_ISREG(sb.st_mode)) goto cleanup; ++ if ((sb.st_mode & 07777) != base_mode || sb.st_nlink != 1) goto cleanup; ++ if (sb.st_size < 2 || sb.st_size >= (off_t)sizeof(line)) goto cleanup; ++ ++ len = read(base_fd, line, sizeof(line)); ++ if (len != (ssize_t)sb.st_size) goto cleanup; ++ line[len] = '\0'; ++ ++ if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup; ++ if (line[len-1] != '\n') goto cleanup; ++ lines_match = (len == pid_len && strcmp(line, pid_line) == 0); ++ } ++ ++if (operation == PID_WRITE) ++ { ++ if (!lines_match) ++ { ++ if (base_fd >= 0) ++ { ++ int error = -1; ++ /* emulate unlinkat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ error = unlink(base); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (error) goto cleanup; ++ (void)close(base_fd); ++ base_fd = -1; ++ } ++ /* emulate openat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ base_fd = open(base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (base_fd < 0) goto cleanup; ++ if (fchmod(base_fd, base_mode) != 0) goto cleanup; ++ if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup; ++ DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path); ++ } ++ } ++else ++ { ++ if (!lines_match) goto cleanup; ++ if (operation == PID_DELETE) ++ { ++ int error = -1; ++ /* emulate unlinkat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ error = unlink(base); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (error) goto cleanup; ++ } ++ } ++ ++success = TRUE; ++errno = 0; ++ ++cleanup: ++if (cwd_fd >= 0) (void)close(cwd_fd); ++if (dir_fd >= 0) (void)close(dir_fd); ++if (base_fd >= 0) (void)close(base_fd); ++return success; ++} ++ ++ ++/* Remove the daemon's pidfile. Note: runs with root privilege, ++as a direct child of the daemon. Does not return. */ ++ ++void ++delete_pid_file(void) ++{ ++const BOOL success = operate_on_pid_file(PID_DELETE, getppid()); ++ ++DEBUG(D_any) ++ debug_printf("delete pid file %s %s: %s\n", pid_file_path, ++ success ? "success" : "failure", strerror(errno)); ++ ++exim_exit(EXIT_SUCCESS, US""); ++} ++ ++ ++/* Called by the daemon; exec a child to get the pid file deleted ++since we may require privs for the containing directory */ ++ ++static void ++daemon_die(void) ++{ ++int pid; ++ ++DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n"); ++#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) ++tls_watch_invalidate(); ++#endif ++ ++if (f.running_in_test_harness || write_pid) ++ { ++ if ((pid = fork()) == 0) ++ { ++ if (override_pid_file_path) ++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3, ++ "-oP", override_pid_file_path, "-oPX"); ++ else ++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX"); ++ ++ /* Control never returns here. */ ++ } ++ if (pid > 0) ++ child_close(pid, 1); ++ } ++exim_exit(EXIT_SUCCESS, US""); ++} ++ ++ + + /************************************************* + * Exim Daemon Mainline * + *************************************************/ + +@@ -1538,32 +1726,18 @@ automatically. Consequently, Exim 4 writ + + The variable daemon_write_pid is used to control this. */ + + if (f.running_in_test_harness || write_pid) + { +- FILE *f; +- +- if (override_pid_file_path) +- pid_file_path = override_pid_file_path; +- +- if (pid_file_path[0] == 0) +- pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); +- +- if ((f = modefopen(pid_file_path, "wb", 0644))) +- { +- (void)fprintf(f, "%d\n", (int)getpid()); +- (void)fclose(f); +- DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path); +- } +- else +- DEBUG(D_any) +- debug_printf("%s\n", string_open_failed(errno, "pid file %s", +- pid_file_path)); ++ const enum pid_op operation = (f.running_in_test_harness ++ || real_uid == root_uid ++ || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK; ++ if (!operate_on_pid_file(operation, getpid())) ++ DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno)); + } + + /* Set up the handler for SIGHUP, which causes a restart of the daemon. */ +- + sighup_seen = FALSE; + signal(SIGHUP, sighup_handler); + + /* Give up root privilege at this point (assuming that exim_uid and exim_gid + are not root). The third argument controls the running of initgroups(). +--- a/src/exim.c ++++ b/src/exim.c +@@ -3042,12 +3042,20 @@ for (i = 1; i < argc; i++) + + else if (Ustrcmp(argrest, "o") == 0) {} + + /* -oP : set pid file path for daemon */ + +- else if (Ustrcmp(argrest, "P") == 0) +- override_pid_file_path = argv[++i]; ++ else if (*argrest == 'P') ++ { ++ if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) ++ exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX " ++ "(uid=%d euid=%d | %d)\n", ++ root_uid, exim_uid, getuid(), geteuid(), real_uid); ++ if (Ustrcmp(argrest, "P") == 0) override_pid_file_path = argv[++i]; ++ else if (Ustrcmp(argrest, "PX") == 0) delete_pid_file(); ++ else badarg = TRUE; ++ } + + /* -or : set timeout for non-SMTP acceptance + -os : set timeout for SMTP acceptance */ + + else if (*argrest == 'r' || *argrest == 's')