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 @@ -2147,6 +2147,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 @@ -2177,6 +2226,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 @@ -2189,37 +2239,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 @@ -2538,7 +2558,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 if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
129 @@ -2553,7 +2573,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 @@ -2867,7 +2887,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 item: %s\n", name, sub[0]);
143 + DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", opname, sub[0]);
146 compare = cond_type == ECOND_INLISTI
147 @@ -2909,14 +2929,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 @@ -2926,7 +2946,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 @@ -2958,7 +2978,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 @@ -2979,7 +2999,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 @@ -2989,7 +3009,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 @@ -3001,11 +3021,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 @@ -3098,19 +3118,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 @@ -3120,7 +3141,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 @@ -3849,6 +3870,56 @@ 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 */
302 /* Return pointer to dewrapped string, with enclosing specified chars removed.
303 The given string is modified on return. Leading whitespace is skipped while
304 looking for the opening wrap character, then the rest is scanned for the trailing
305 @@ -3905,7 +3976,7 @@ The element may itself be an object or a
306 Return NULL when the list is empty.
311 json_nextinlist(const uschar ** list)
313 unsigned array_depth = 0, object_depth = 0;
314 @@ -6243,9 +6314,10 @@ while (*s != 0)
320 const uschar *srclist, *cmp, *xtract;
322 + uschar * opname, * srcitem;
323 const uschar *dstlist = NULL, *dstkeylist = NULL;
325 uschar *save_iterate_item = iterate_item;
326 @@ -6280,6 +6352,25 @@ while (*s != 0)
327 goto EXPAND_FAILED_CURLY;
330 + if ((cond_type = identify_operator(&cmp, &opname)) == -1)
332 + if (!expand_string_message)
333 + expand_string_message = string_sprintf("unknown condition \"%s\"", s);
334 + goto EXPAND_FAILED;
338 + case ECOND_NUM_L: case ECOND_NUM_LE:
339 + case ECOND_NUM_G: case ECOND_NUM_GE:
340 + case ECOND_STR_GE: case ECOND_STR_GEI: case ECOND_STR_GT: case ECOND_STR_GTI:
341 + case ECOND_STR_LE: case ECOND_STR_LEI: case ECOND_STR_LT: case ECOND_STR_LTI:
345 + expand_string_message = US"comparator not handled for sort";
346 + goto EXPAND_FAILED;
349 while (isspace(*s)) s++;
352 @@ -6307,11 +6398,10 @@ while (*s != 0)
353 if (skipping) continue;
355 while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
359 + uschar * srcfield, * dstitem;
360 gstring * newlist = NULL;
361 gstring * newkeylist = NULL;
364 DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
366 @@ -6332,25 +6422,15 @@ while (*s != 0)
367 while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
373 /* field for comparison */
374 if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
377 - /* build and run condition string */
378 - expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
380 - DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr);
381 - if (!eval_condition(expr, &resetok, &before))
383 - expand_string_message = string_sprintf("comparison in sort: %s",
385 - goto EXPAND_FAILED;
387 + /* String-comparator names start with a letter; numeric names do not */
390 + if (sortsbefore(cond_type, isalpha(opname[0]),
391 + srcfield, dstfield))
393 /* New-item sorts before this dst-item. Append new-item,
394 then dst-item, then remainder of dst list. */