Import Debian changes 4.92-8+deb10u6
[hcoop/debian/exim4.git] / debian / patches / 84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch
CommitLineData
0c0c20aa
AM
1From 5fec3406547fd1e46838a76f000102beb6bfe468 Mon Sep 17 00:00:00 2001
2From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
3Date: Sun, 14 Mar 2021 12:16:57 +0100
4Subject: [PATCH 24/29] CVE-2020-28008: Assorted attacks in Exim's spool
5 directory
6
7We patch dbfn_open() by introducing two functions priv_drop_temp() and
8priv_restore() (inspired by OpenSSH's functions temporarily_use_uid()
9and restore_uid()), which temporarily drop and restore root privileges
10thanks to seteuid(). This goes against Exim's developers' wishes ("Exim
11(the project) doesn't trust seteuid to work reliably") but, to the best
12of our knowledge, seteuid() works everywhere and is the only way to
13securely fix dbfn_open().
14
15(cherry picked from commit 18da59151dbafa89be61c63580bdb295db36e374)
16(cherry picked from commit b05dc3573f4cd476482374b0ac0393153d344338)
17---
18 doc/ChangeLog | 6 +++
19 src/dbfn.c | 110 +++++++++++++++++++++++++-----------------
20 test/stderr/0275 | 2 +-
21 test/stderr/0278 | 2 +-
22 test/stderr/0386 | 2 +-
23 test/stderr/0388 | 2 +-
24 test/stderr/0402 | 2 +-
25 test/stderr/0403 | 2 +-
26 test/stderr/0404 | 2 +-
27 test/stderr/0408 | 2 +-
28 test/stderr/0487 | 2 +-
29 11 files changed, 80 insertions(+), 54 deletions(-)
30
31diff --git a/doc/ChangeLog b/doc/ChangeLog
32index 5741fb212..b32347c5b 100644
33--- a/doc/ChangeLog
34+++ b/doc/ChangeLog
35@@ -14,6 +14,12 @@ JH/42 Bug 2545: Fix CHUNKING for all RCPT commands rejected. Previously we
36
37 Exim version 4.92.2
38 -------------------
39+QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim
40+ runtime user.
41+
42+QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of
43+ the Exim runtime user are allowed to create files.
44+
45
46 HS/01 Handle trailing backslash gracefully. (CVE-2019-15846)
47
48diff --git a/src/dbfn.c b/src/dbfn.c
49index 336cfe73e..902756508 100644
50--- a/src/dbfn.c
51+++ b/src/dbfn.c
52@@ -59,6 +59,66 @@ log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg);
53
54
55
56+static enum {
57+ PRIV_DROPPING, PRIV_DROPPED,
58+ PRIV_RESTORING, PRIV_RESTORED
59+} priv_state = PRIV_RESTORED;
60+
61+static uid_t priv_euid;
62+static gid_t priv_egid;
63+static gid_t priv_groups[EXIM_GROUPLIST_SIZE + 1];
64+static int priv_ngroups;
65+
66+/* Inspired by OpenSSH's temporarily_use_uid(). Thanks! */
67+
68+static void
69+priv_drop_temp(const uid_t temp_uid, const gid_t temp_gid)
70+{
71+if (priv_state != PRIV_RESTORED) _exit(EXIT_FAILURE);
72+priv_state = PRIV_DROPPING;
73+
74+priv_euid = geteuid();
75+if (priv_euid == root_uid)
76+ {
77+ priv_egid = getegid();
78+ priv_ngroups = getgroups(nelem(priv_groups), priv_groups);
79+ if (priv_ngroups < 0) _exit(EXIT_FAILURE);
80+
81+ if (priv_ngroups > 0 && setgroups(1, &temp_gid) != 0) _exit(EXIT_FAILURE);
82+ if (setegid(temp_gid) != 0) _exit(EXIT_FAILURE);
83+ if (seteuid(temp_uid) != 0) _exit(EXIT_FAILURE);
84+
85+ if (geteuid() != temp_uid) _exit(EXIT_FAILURE);
86+ if (getegid() != temp_gid) _exit(EXIT_FAILURE);
87+ }
88+
89+priv_state = PRIV_DROPPED;
90+}
91+
92+/* Inspired by OpenSSH's restore_uid(). Thanks! */
93+
94+static void
95+priv_restore(void)
96+{
97+if (priv_state != PRIV_DROPPED) _exit(EXIT_FAILURE);
98+priv_state = PRIV_RESTORING;
99+
100+if (priv_euid == root_uid)
101+ {
102+ if (seteuid(priv_euid) != 0) _exit(EXIT_FAILURE);
103+ if (setegid(priv_egid) != 0) _exit(EXIT_FAILURE);
104+ if (priv_ngroups > 0 && setgroups(priv_ngroups, priv_groups) != 0) _exit(EXIT_FAILURE);
105+
106+ if (geteuid() != priv_euid) _exit(EXIT_FAILURE);
107+ if (getegid() != priv_egid) _exit(EXIT_FAILURE);
108+ }
109+
110+priv_state = PRIV_RESTORED;
111+}
112+
113+
114+
115+
116 /*************************************************
117 * Open and lock a database file *
118 *************************************************/
119@@ -89,7 +149,6 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
120 {
121 int rc, save_errno;
122 BOOL read_only = flags == O_RDONLY;
123-BOOL created = FALSE;
124 flock_t lock_data;
125 uschar dirname[256], filename[256];
126
127@@ -111,12 +170,13 @@ exists, there is no error. */
128 snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
129 snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
130
131+priv_drop_temp(exim_uid, exim_gid);
132 if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
133 {
134- created = TRUE;
135 (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
136 dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
137 }
138+priv_restore();
139
140 if (dbblock->lockfd < 0)
141 {
142@@ -165,57 +225,17 @@ it easy to pin this down, there are now debug statements on either side of the
143 open call. */
144
145 snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name);
146-EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
147
148+priv_drop_temp(exim_uid, exim_gid);
149+EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
150 if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
151 {
152 DEBUG(D_hints_lookup)
153 debug_printf_indent("%s appears not to exist: trying to create\n", filename);
154- created = TRUE;
155 EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
156 }
157-
158 save_errno = errno;
159-
160-/* If we are running as root and this is the first access to the database, its
161-files will be owned by root. We want them to be owned by exim. We detect this
162-situation by noting above when we had to create the lock file or the database
163-itself. Because the different dbm libraries use different extensions for their
164-files, I don't know of any easier way of arranging this than scanning the
165-directory for files with the appropriate base name. At least this deals with
166-the lock file at the same time. Also, the directory will typically have only
167-half a dozen files, so the scan will be quick.
168-
169-This code is placed here, before the test for successful opening, because there
170-was a case when a file was created, but the DBM library still returned NULL
171-because of some problem. It also sorts out the lock file if that was created
172-but creation of the database file failed. */
173-
174-if (created && geteuid() == root_uid)
175- {
176- DIR *dd;
177- struct dirent *ent;
178- uschar *lastname = Ustrrchr(filename, '/') + 1;
179- int namelen = Ustrlen(name);
180-
181- *lastname = 0;
182- dd = opendir(CS filename);
183-
184- while ((ent = readdir(dd)))
185- if (Ustrncmp(ent->d_name, name, namelen) == 0)
186- {
187- struct stat statbuf;
188- Ustrcpy(lastname, ent->d_name);
189- if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
190- {
191- DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
192- if (Uchown(filename, exim_uid, exim_gid))
193- DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename);
194- }
195- }
196-
197- closedir(dd);
198- }
199+priv_restore();
200
201 /* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
202 log the event - also for debugging - but debug only if the file just doesn't
203--
2042.30.2
205