Merge branch 'debian'
[hcoop/debian/exim4.git] / 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 (file)
index 0000000..3e73ae1
--- /dev/null
@@ -0,0 +1,303 @@
+From c166890023f56388cb3482cff3def04171a488c4 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+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 <name>: 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 <n>: set timeout for non-SMTP acceptance
+        -os <n>: set timeout for SMTP acceptance */
+     else if (*argrest == 'r' || *argrest == 's')