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
7 (cherry picked from commit 5c887f836e4d8e3f79da1c15565b56b40d9bd0dd)
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
17 @@ -2115,6 +2115,55 @@ return ret;
21 +/************************************************/
22 +/* Return offset in ops table, or -1 if not found.
23 +Repoint to just after the operator in the string.
26 + ss string representation of operator
27 + opname split-out operator name
31 +identify_operator(const uschar ** ss, uschar ** opname)
33 +const uschar * s = *ss;
36 +/* Numeric comparisons are symbolic */
38 +if (*s == '=' || *s == '>' || *s == '<')
50 +/* All other conditions are named */
53 + s = read_name(name, sizeof(name), s, US"_");
56 +/* If we haven't read a name, it means some non-alpha character is first. */
60 + expand_string_message = string_sprintf("condition name expected, "
61 + "but found \"%.16s\"", s);
65 + *opname = string_copy(name);
67 +return chop_match(name, cond_table, nelem(cond_table));
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];
79 const uschar *sub[10];
81 @@ -2157,37 +2207,7 @@ for (;;)
82 if (*s == '!') { testfor = !testfor; s++; } else break;
85 -/* Numeric comparisons are symbolic */
87 -if (*s == '=' || *s == '>' || *s == '<')
99 -/* All other conditions are named */
101 -else s = read_name(name, 256, s, US"_");
103 -/* If we haven't read a name, it means some non-alpha character is first. */
107 - expand_string_message = string_sprintf("condition name expected, "
108 - "but found \"%.16s\"", s);
112 -/* Find which condition we are dealing with, and switch on it */
114 -cond_type = chop_match(name, cond_table, nelem(cond_table));
116 +switch(cond_type = identify_operator(&s, &opname))
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)
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);
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. */
133 - if (!isalpha(name[0]) && yield != NULL)
134 + if (!isalpha(opname[0]) && yield != NULL)
138 @@ -2832,7 +2852,7 @@ switch(cond_type)
139 uschar *save_iterate_item = iterate_item;
140 int (*compare)(const uschar *, const uschar *);
142 - DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
143 + DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
146 compare = cond_type == ECOND_INLISTI
147 @@ -2871,14 +2891,14 @@ switch(cond_type)
148 if (*s != '{') /* }-for-text-editors */
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);
156 if (!(s = eval_condition(s+1, resetok, subcondptr)))
158 expand_string_message = string_sprintf("%s inside \"%s{...}\" condition",
159 - expand_string_message, name);
160 + expand_string_message, opname);
163 while (isspace(*s)) s++;
164 @@ -2888,7 +2908,7 @@ switch(cond_type)
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);
173 @@ -2920,7 +2940,7 @@ switch(cond_type)
175 uschar *save_iterate_item = iterate_item;
177 - DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
178 + DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
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)))
185 expand_string_message = string_sprintf("%s inside \"%s\" condition",
186 - expand_string_message, name);
187 + expand_string_message, opname);
190 while (isspace(*s)) s++;
191 @@ -2951,7 +2971,7 @@ switch(cond_type)
193 /* {-for-text-editors */
194 expand_string_message = string_sprintf("missing } at end of condition "
195 - "inside \"%s\"", name);
196 + "inside \"%s\"", opname);
200 @@ -2963,11 +2983,11 @@ switch(cond_type)
201 if (!eval_condition(sub[1], resetok, &tempcond))
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;
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");
213 if (yield != NULL) *yield = (tempcond == testfor);
214 @@ -3060,19 +3080,20 @@ switch(cond_type)
215 /* Unknown condition */
218 - expand_string_message = string_sprintf("unknown condition \"%s\"", name);
220 + if (!expand_string_message || !*expand_string_message)
221 + expand_string_message = string_sprintf("unknown condition \"%s\"", opname);
223 } /* End switch on condition type */
225 /* Missing braces at start and end of data */
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);
232 COND_FAILED_CURLY_END:
233 expand_string_message = string_sprintf("missing } at end of \"%s\" condition",
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",
248 @@ -3793,6 +3814,58 @@ return x;
252 +/************************************************/
253 +/* Comparison operation for sort expansion. We need to avoid
254 +re-expanding the fields being compared, so need a custom routine.
257 + cond_type Comparison operator code
258 + leftarg, rightarg Arguments for comparison
260 +Return true iff (leftarg compare rightarg)
264 +sortsbefore(int cond_type, BOOL alpha_cond,
265 + const uschar * leftarg, const uschar * rightarg)
267 +int_eximarith_t l_num, r_num;
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;
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;
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;
298 +return FALSE; /* should not happen */
305 /*************************************************
307 @@ -5904,9 +5977,10 @@ while (*s != 0)
313 const uschar *srclist, *cmp, *xtract;
315 + uschar * opname, * srcitem;
316 const uschar *dstlist = NULL, *dstkeylist = NULL;
318 uschar *save_iterate_item = iterate_item;
319 @@ -5941,6 +6015,25 @@ while (*s != 0)
320 goto EXPAND_FAILED_CURLY;
323 + if ((cond_type = identify_operator(&cmp, &opname)) == -1)
325 + if (!expand_string_message)
326 + expand_string_message = string_sprintf("unknown condition \"%s\"", s);
327 + goto EXPAND_FAILED;
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:
338 + expand_string_message = US"comparator not handled for sort";
339 + goto EXPAND_FAILED;
342 while (isspace(*s)) s++;
345 @@ -5969,10 +6062,9 @@ while (*s != 0)
347 while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
350 + uschar * srcfield, * dstitem;
351 uschar * newlist = NULL;
352 uschar * newkeylist = NULL;
355 DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
357 @@ -5993,25 +6085,15 @@ while (*s != 0)
358 while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
364 /* field for comparison */
365 if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
368 - /* build and run condition string */
369 - expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
371 - DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr);
372 - if (!eval_condition(expr, &resetok, &before))
374 - expand_string_message = string_sprintf("comparison in sort: %s",
376 - goto EXPAND_FAILED;
378 + /* String-comparator names start with a letter; numeric names do not */
381 + if (sortsbefore(cond_type, isalpha(opname[0]),
382 + srcfield, dstfield))
384 /* New-item sorts before this dst-item. Append new-item,
385 then dst-item, then remainder of dst list. */