Commit | Line | Data |
---|---|---|
b032efff AM |
1 | From cf84d126bc1f04746eb7c8e8b3468f7e70add3ec Mon Sep 17 00:00:00 2001 |
2 | From: Jeremy Harris <jgh146exb@wizmail.org> | |
3 | Date: Fri, 5 Jul 2019 15:38:15 +0100 | |
4 | Subject: [PATCH] Avoid re-expansion in ${sort } CVE-2019-13917 | |
5 | OVE-20190718-0006 | |
6 | ||
7 | (cherry picked from commit 5c887f836e4d8e3f79da1c15565b56b40d9bd0dd) | |
8 | --- | |
9 | doc/ChangeLog | 6 ++ | |
10 | doc/doc-txt/cve-2019-13917 | 46 ++++++++ | |
11 | src/expand.c | 214 +++++++++++++++++++++++++------------ | |
12 | 3 files changed, 199 insertions(+), 67 deletions(-) | |
13 | create mode 100644 doc/doc-txt/cve-2019-13917 | |
14 | ||
15 | --- a/src/expand.c | |
16 | +++ b/src/expand.c | |
17 | @@ -2115,6 +2115,55 @@ return ret; | |
18 | ||
19 | ||
20 | ||
21 | +/************************************************/ | |
22 | +/* Return offset in ops table, or -1 if not found. | |
23 | +Repoint to just after the operator in the string. | |
24 | + | |
25 | +Argument: | |
26 | + ss string representation of operator | |
27 | + opname split-out operator name | |
28 | +*/ | |
29 | + | |
30 | +static int | |
31 | +identify_operator(const uschar ** ss, uschar ** opname) | |
32 | +{ | |
33 | +const uschar * s = *ss; | |
34 | +uschar name[256]; | |
35 | + | |
36 | +/* Numeric comparisons are symbolic */ | |
37 | + | |
38 | +if (*s == '=' || *s == '>' || *s == '<') | |
39 | + { | |
40 | + int p = 0; | |
41 | + name[p++] = *s++; | |
42 | + if (*s == '=') | |
43 | + { | |
44 | + name[p++] = '='; | |
45 | + s++; | |
46 | + } | |
47 | + name[p] = 0; | |
48 | + } | |
49 | + | |
50 | +/* All other conditions are named */ | |
51 | + | |
52 | +else | |
53 | + s = read_name(name, sizeof(name), s, US"_"); | |
54 | +*ss = s; | |
55 | + | |
56 | +/* If we haven't read a name, it means some non-alpha character is first. */ | |
57 | + | |
58 | +if (!name[0]) | |
59 | + { | |
60 | + expand_string_message = string_sprintf("condition name expected, " | |
61 | + "but found \"%.16s\"", s); | |
62 | + return -1; | |
63 | + } | |
64 | +if (opname) | |
65 | + *opname = string_copy(name); | |
66 | + | |
67 | +return chop_match(name, cond_table, nelem(cond_table)); | |
68 | +} | |
69 | + | |
70 | ||
71 | /************************************************* | |
72 | * Read and evaluate a condition * | |
73 | @@ -2145,6 +2194,7 @@ BOOL sub2_honour_dollar = TRUE; | |
74 | int i, rc, cond_type, roffset; | |
75 | int_eximarith_t num[2]; | |
76 | struct stat statbuf; | |
77 | +uschar * opname; | |
78 | uschar name[256]; | |
79 | const uschar *sub[10]; | |
80 | ||
81 | @@ -2157,37 +2207,7 @@ for (;;) | |
82 | if (*s == '!') { testfor = !testfor; s++; } else break; | |
83 | } | |
84 | ||
85 | -/* Numeric comparisons are symbolic */ | |
86 | - | |
87 | -if (*s == '=' || *s == '>' || *s == '<') | |
88 | - { | |
89 | - int p = 0; | |
90 | - name[p++] = *s++; | |
91 | - if (*s == '=') | |
92 | - { | |
93 | - name[p++] = '='; | |
94 | - s++; | |
95 | - } | |
96 | - name[p] = 0; | |
97 | - } | |
98 | - | |
99 | -/* All other conditions are named */ | |
100 | - | |
101 | -else s = read_name(name, 256, s, US"_"); | |
102 | - | |
103 | -/* If we haven't read a name, it means some non-alpha character is first. */ | |
104 | - | |
105 | -if (name[0] == 0) | |
106 | - { | |
107 | - expand_string_message = string_sprintf("condition name expected, " | |
108 | - "but found \"%.16s\"", s); | |
109 | - return NULL; | |
110 | - } | |
111 | - | |
112 | -/* Find which condition we are dealing with, and switch on it */ | |
113 | - | |
114 | -cond_type = chop_match(name, cond_table, nelem(cond_table)); | |
115 | -switch(cond_type) | |
116 | +switch(cond_type = identify_operator(&s, &opname)) | |
117 | { | |
118 | /* def: tests for a non-empty variable, or for the existence of a header. If | |
119 | yield == NULL we are in a skipping state, and don't care about the answer. */ | |
120 | @@ -2506,7 +2526,7 @@ switch(cond_type) | |
121 | { | |
122 | if (i == 0) goto COND_FAILED_CURLY_START; | |
123 | expand_string_message = string_sprintf("missing 2nd string in {} " | |
124 | - "after \"%s\"", name); | |
125 | + "after \"%s\"", opname); | |
126 | return NULL; | |
127 | } | |
128 | sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL, | |
129 | @@ -2518,7 +2538,7 @@ switch(cond_type) | |
130 | conditions that compare numbers do not start with a letter. This just saves | |
131 | checking for them individually. */ | |
132 | ||
133 | - if (!isalpha(name[0]) && yield != NULL) | |
134 | + if (!isalpha(opname[0]) && yield != NULL) | |
135 | if (sub[i][0] == 0) | |
136 | { | |
137 | num[i] = 0; | |
138 | @@ -2832,7 +2852,7 @@ switch(cond_type) | |
139 | uschar *save_iterate_item = iterate_item; | |
140 | int (*compare)(const uschar *, const uschar *); | |
141 | ||
142 | - DEBUG(D_expand) debug_printf_indent("condition: %s\n", name); | |
143 | + DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname); | |
144 | ||
145 | tempcond = FALSE; | |
146 | compare = cond_type == ECOND_INLISTI | |
147 | @@ -2871,14 +2891,14 @@ switch(cond_type) | |
148 | if (*s != '{') /* }-for-text-editors */ | |
149 | { | |
150 | expand_string_message = string_sprintf("each subcondition " | |
151 | - "inside an \"%s{...}\" condition must be in its own {}", name); | |
152 | + "inside an \"%s{...}\" condition must be in its own {}", opname); | |
153 | return NULL; | |
154 | } | |
155 | ||
156 | if (!(s = eval_condition(s+1, resetok, subcondptr))) | |
157 | { | |
158 | expand_string_message = string_sprintf("%s inside \"%s{...}\" condition", | |
159 | - expand_string_message, name); | |
160 | + expand_string_message, opname); | |
161 | return NULL; | |
162 | } | |
163 | while (isspace(*s)) s++; | |
164 | @@ -2888,7 +2908,7 @@ switch(cond_type) | |
165 | { | |
166 | /* {-for-text-editors */ | |
167 | expand_string_message = string_sprintf("missing } at end of condition " | |
168 | - "inside \"%s\" group", name); | |
169 | + "inside \"%s\" group", opname); | |
170 | return NULL; | |
171 | } | |
172 | ||
173 | @@ -2920,7 +2940,7 @@ switch(cond_type) | |
174 | int sep = 0; | |
175 | uschar *save_iterate_item = iterate_item; | |
176 | ||
177 | - DEBUG(D_expand) debug_printf_indent("condition: %s\n", name); | |
178 | + DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname); | |
179 | ||
180 | while (isspace(*s)) s++; | |
181 | if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ | |
182 | @@ -2941,7 +2961,7 @@ switch(cond_type) | |
183 | if (!(s = eval_condition(sub[1], resetok, NULL))) | |
184 | { | |
185 | expand_string_message = string_sprintf("%s inside \"%s\" condition", | |
186 | - expand_string_message, name); | |
187 | + expand_string_message, opname); | |
188 | return NULL; | |
189 | } | |
190 | while (isspace(*s)) s++; | |
191 | @@ -2951,7 +2971,7 @@ switch(cond_type) | |
192 | { | |
193 | /* {-for-text-editors */ | |
194 | expand_string_message = string_sprintf("missing } at end of condition " | |
195 | - "inside \"%s\"", name); | |
196 | + "inside \"%s\"", opname); | |
197 | return NULL; | |
198 | } | |
199 | ||
200 | @@ -2963,11 +2983,11 @@ switch(cond_type) | |
201 | if (!eval_condition(sub[1], resetok, &tempcond)) | |
202 | { | |
203 | expand_string_message = string_sprintf("%s inside \"%s\" condition", | |
204 | - expand_string_message, name); | |
205 | + expand_string_message, opname); | |
206 | iterate_item = save_iterate_item; | |
207 | return NULL; | |
208 | } | |
209 | - DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name, | |
210 | + DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", opname, | |
211 | tempcond? "true":"false"); | |
212 | ||
213 | if (yield != NULL) *yield = (tempcond == testfor); | |
214 | @@ -3060,19 +3080,20 @@ switch(cond_type) | |
215 | /* Unknown condition */ | |
216 | ||
217 | default: | |
218 | - expand_string_message = string_sprintf("unknown condition \"%s\"", name); | |
219 | - return NULL; | |
220 | + if (!expand_string_message || !*expand_string_message) | |
221 | + expand_string_message = string_sprintf("unknown condition \"%s\"", opname); | |
222 | + return NULL; | |
223 | } /* End switch on condition type */ | |
224 | ||
225 | /* Missing braces at start and end of data */ | |
226 | ||
227 | COND_FAILED_CURLY_START: | |
228 | -expand_string_message = string_sprintf("missing { after \"%s\"", name); | |
229 | +expand_string_message = string_sprintf("missing { after \"%s\"", opname); | |
230 | return NULL; | |
231 | ||
232 | COND_FAILED_CURLY_END: | |
233 | expand_string_message = string_sprintf("missing } at end of \"%s\" condition", | |
234 | - name); | |
235 | + opname); | |
236 | return NULL; | |
237 | ||
238 | /* A condition requires code that is not compiled */ | |
239 | @@ -3082,7 +3103,7 @@ return NULL; | |
240 | !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET) | |
241 | COND_FAILED_NOT_COMPILED: | |
242 | expand_string_message = string_sprintf("support for \"%s\" not compiled", | |
243 | - name); | |
244 | + opname); | |
245 | return NULL; | |
246 | #endif | |
247 | } | |
248 | @@ -3793,6 +3814,58 @@ return x; | |
249 | } | |
250 | ||
251 | ||
252 | +/************************************************/ | |
253 | +/* Comparison operation for sort expansion. We need to avoid | |
254 | +re-expanding the fields being compared, so need a custom routine. | |
255 | + | |
256 | +Arguments: | |
257 | + cond_type Comparison operator code | |
258 | + leftarg, rightarg Arguments for comparison | |
259 | + | |
260 | +Return true iff (leftarg compare rightarg) | |
261 | +*/ | |
262 | + | |
263 | +static BOOL | |
264 | +sortsbefore(int cond_type, BOOL alpha_cond, | |
265 | + const uschar * leftarg, const uschar * rightarg) | |
266 | +{ | |
267 | +int_eximarith_t l_num, r_num; | |
268 | + | |
269 | +if (!alpha_cond) | |
270 | + { | |
271 | + l_num = expanded_string_integer(leftarg, FALSE); | |
272 | + if (expand_string_message) return FALSE; | |
273 | + r_num = expanded_string_integer(rightarg, FALSE); | |
274 | + if (expand_string_message) return FALSE; | |
275 | + | |
276 | + switch (cond_type) | |
277 | + { | |
278 | + case ECOND_NUM_G: return l_num > r_num; | |
279 | + case ECOND_NUM_GE: return l_num >= r_num; | |
280 | + case ECOND_NUM_L: return l_num < r_num; | |
281 | + case ECOND_NUM_LE: return l_num <= r_num; | |
282 | + default: break; | |
283 | + } | |
284 | + } | |
285 | +else | |
286 | + switch (cond_type) | |
287 | + { | |
288 | + case ECOND_STR_LT: return Ustrcmp (leftarg, rightarg) < 0; | |
289 | + case ECOND_STR_LTI: return strcmpic(leftarg, rightarg) < 0; | |
290 | + case ECOND_STR_LE: return Ustrcmp (leftarg, rightarg) <= 0; | |
291 | + case ECOND_STR_LEI: return strcmpic(leftarg, rightarg) <= 0; | |
292 | + case ECOND_STR_GT: return Ustrcmp (leftarg, rightarg) > 0; | |
293 | + case ECOND_STR_GTI: return strcmpic(leftarg, rightarg) > 0; | |
294 | + case ECOND_STR_GE: return Ustrcmp (leftarg, rightarg) >= 0; | |
295 | + case ECOND_STR_GEI: return strcmpic(leftarg, rightarg) >= 0; | |
296 | + default: break; | |
297 | + } | |
298 | +return FALSE; /* should not happen */ | |
299 | +} | |
300 | + | |
301 | + | |
302 | + | |
303 | + | |
304 | ||
305 | /************************************************* | |
306 | * Expand string * | |
307 | @@ -5904,9 +5977,10 @@ while (*s != 0) | |
308 | ||
309 | case EITEM_SORT: | |
310 | { | |
311 | + int cond_type; | |
312 | int sep = 0; | |
313 | const uschar *srclist, *cmp, *xtract; | |
314 | - uschar *srcitem; | |
315 | + uschar * opname, * srcitem; | |
316 | const uschar *dstlist = NULL, *dstkeylist = NULL; | |
317 | uschar * tmp; | |
318 | uschar *save_iterate_item = iterate_item; | |
319 | @@ -5941,6 +6015,25 @@ while (*s != 0) | |
320 | goto EXPAND_FAILED_CURLY; | |
321 | } | |
322 | ||
323 | + if ((cond_type = identify_operator(&cmp, &opname)) == -1) | |
324 | + { | |
325 | + if (!expand_string_message) | |
326 | + expand_string_message = string_sprintf("unknown condition \"%s\"", s); | |
327 | + goto EXPAND_FAILED; | |
328 | + } | |
329 | + switch(cond_type) | |
330 | + { | |
331 | + case ECOND_NUM_L: case ECOND_NUM_LE: | |
332 | + case ECOND_NUM_G: case ECOND_NUM_GE: | |
333 | + case ECOND_STR_GE: case ECOND_STR_GEI: case ECOND_STR_GT: case ECOND_STR_GTI: | |
334 | + case ECOND_STR_LE: case ECOND_STR_LEI: case ECOND_STR_LT: case ECOND_STR_LTI: | |
335 | + break; | |
336 | + | |
337 | + default: | |
338 | + expand_string_message = US"comparator not handled for sort"; | |
339 | + goto EXPAND_FAILED; | |
340 | + } | |
341 | + | |
342 | while (isspace(*s)) s++; | |
343 | if (*s++ != '{') | |
344 | { | |
345 | @@ -5969,10 +6062,9 @@ while (*s != 0) | |
346 | ||
347 | while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0))) | |
348 | { | |
349 | - uschar * dstitem; | |
350 | + uschar * srcfield, * dstitem; | |
351 | uschar * newlist = NULL; | |
352 | uschar * newkeylist = NULL; | |
353 | - uschar * srcfield; | |
354 | ||
355 | DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem); | |
356 | ||
357 | @@ -5993,25 +6085,15 @@ while (*s != 0) | |
358 | while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0))) | |
359 | { | |
360 | uschar * dstfield; | |
361 | - uschar * expr; | |
362 | - BOOL before; | |
363 | ||
364 | /* field for comparison */ | |
365 | if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) | |
366 | goto sort_mismatch; | |
367 | ||
368 | - /* build and run condition string */ | |
369 | - expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield); | |
370 | - | |
371 | - DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr); | |
372 | - if (!eval_condition(expr, &resetok, &before)) | |
373 | - { | |
374 | - expand_string_message = string_sprintf("comparison in sort: %s", | |
375 | - expr); | |
376 | - goto EXPAND_FAILED; | |
377 | - } | |
378 | + /* String-comparator names start with a letter; numeric names do not */ | |
379 | ||
380 | - if (before) | |
381 | + if (sortsbefore(cond_type, isalpha(opname[0]), | |
382 | + srcfield, dstfield)) | |
383 | { | |
384 | /* New-item sorts before this dst-item. Append new-item, | |
385 | then dst-item, then remainder of dst list. */ |