diff options
author | Colin Watson <cjwatson@debian.org> | 2019-03-01 09:57:53 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2019-03-01 09:58:48 +0000 |
commit | bc8b39092a460d8e7e9d8d42d32f2c964d18fec3 (patch) | |
tree | 258baf7478e0e5737351c704c6313d5973cdd81f /debian/patches/scp-handle-braces.patch | |
parent | b85f89c2c0ee71d4eee9c5ce3753eb89bc5fc715 (diff) | |
parent | 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab (diff) |
Handle shell-style brace expansions in scp checks
Diffstat (limited to 'debian/patches/scp-handle-braces.patch')
-rw-r--r-- | debian/patches/scp-handle-braces.patch | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/debian/patches/scp-handle-braces.patch b/debian/patches/scp-handle-braces.patch new file mode 100644 index 000000000..0cbdcfdc0 --- /dev/null +++ b/debian/patches/scp-handle-braces.patch | |||
@@ -0,0 +1,353 @@ | |||
1 | From 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab Mon Sep 17 00:00:00 2001 | ||
2 | From: "djm@openbsd.org" <djm@openbsd.org> | ||
3 | Date: Sun, 10 Feb 2019 11:15:52 +0000 | ||
4 | Subject: upstream: when checking that filenames sent by the server side | ||
5 | |||
6 | match what the client requested, be prepared to handle shell-style brace | ||
7 | alternations, e.g. "{foo,bar}". | ||
8 | |||
9 | "looks good to me" millert@ + in snaps for the last week courtesy | ||
10 | deraadt@ | ||
11 | |||
12 | OpenBSD-Commit-ID: 3b1ce7639b0b25b2248e3a30f561a548f6815f3e | ||
13 | |||
14 | Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=3d896c157c722bc47adca51a58dca859225b5874 | ||
15 | Bug-Debian: https://bugs.debian.org/923486 | ||
16 | Last-Update: 2019-03-01 | ||
17 | |||
18 | Patch-Name: scp-handle-braces.patch | ||
19 | --- | ||
20 | scp.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- | ||
21 | 1 file changed, 269 insertions(+), 11 deletions(-) | ||
22 | |||
23 | diff --git a/scp.c b/scp.c | ||
24 | index 035037bcc..3888baab0 100644 | ||
25 | --- a/scp.c | ||
26 | +++ b/scp.c | ||
27 | @@ -635,6 +635,253 @@ parse_scp_uri(const char *uri, char **userp, char **hostp, int *portp, | ||
28 | return r; | ||
29 | } | ||
30 | |||
31 | +/* Appends a string to an array; returns 0 on success, -1 on alloc failure */ | ||
32 | +static int | ||
33 | +append(char *cp, char ***ap, size_t *np) | ||
34 | +{ | ||
35 | + char **tmp; | ||
36 | + | ||
37 | + if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL) | ||
38 | + return -1; | ||
39 | + tmp[(*np)] = cp; | ||
40 | + (*np)++; | ||
41 | + *ap = tmp; | ||
42 | + return 0; | ||
43 | +} | ||
44 | + | ||
45 | +/* | ||
46 | + * Finds the start and end of the first brace pair in the pattern. | ||
47 | + * returns 0 on success or -1 for invalid patterns. | ||
48 | + */ | ||
49 | +static int | ||
50 | +find_brace(const char *pattern, int *startp, int *endp) | ||
51 | +{ | ||
52 | + int i; | ||
53 | + int in_bracket, brace_level; | ||
54 | + | ||
55 | + *startp = *endp = -1; | ||
56 | + in_bracket = brace_level = 0; | ||
57 | + for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) { | ||
58 | + switch (pattern[i]) { | ||
59 | + case '\\': | ||
60 | + /* skip next character */ | ||
61 | + if (pattern[i + 1] != '\0') | ||
62 | + i++; | ||
63 | + break; | ||
64 | + case '[': | ||
65 | + in_bracket = 1; | ||
66 | + break; | ||
67 | + case ']': | ||
68 | + in_bracket = 0; | ||
69 | + break; | ||
70 | + case '{': | ||
71 | + if (in_bracket) | ||
72 | + break; | ||
73 | + if (pattern[i + 1] == '}') { | ||
74 | + /* Protect a single {}, for find(1), like csh */ | ||
75 | + i++; /* skip */ | ||
76 | + break; | ||
77 | + } | ||
78 | + if (*startp == -1) | ||
79 | + *startp = i; | ||
80 | + brace_level++; | ||
81 | + break; | ||
82 | + case '}': | ||
83 | + if (in_bracket) | ||
84 | + break; | ||
85 | + if (*startp < 0) { | ||
86 | + /* Unbalanced brace */ | ||
87 | + return -1; | ||
88 | + } | ||
89 | + if (--brace_level <= 0) | ||
90 | + *endp = i; | ||
91 | + break; | ||
92 | + } | ||
93 | + } | ||
94 | + /* unbalanced brackets/braces */ | ||
95 | + if (*endp < 0 && (*startp >= 0 || in_bracket)) | ||
96 | + return -1; | ||
97 | + return 0; | ||
98 | +} | ||
99 | + | ||
100 | +/* | ||
101 | + * Assembles and records a successfully-expanded pattern, returns -1 on | ||
102 | + * alloc failure. | ||
103 | + */ | ||
104 | +static int | ||
105 | +emit_expansion(const char *pattern, int brace_start, int brace_end, | ||
106 | + int sel_start, int sel_end, char ***patternsp, size_t *npatternsp) | ||
107 | +{ | ||
108 | + char *cp; | ||
109 | + int o = 0, tail_len = strlen(pattern + brace_end + 1); | ||
110 | + | ||
111 | + if ((cp = malloc(brace_start + (sel_end - sel_start) + | ||
112 | + tail_len + 1)) == NULL) | ||
113 | + return -1; | ||
114 | + | ||
115 | + /* Pattern before initial brace */ | ||
116 | + if (brace_start > 0) { | ||
117 | + memcpy(cp, pattern, brace_start); | ||
118 | + o = brace_start; | ||
119 | + } | ||
120 | + /* Current braced selection */ | ||
121 | + if (sel_end - sel_start > 0) { | ||
122 | + memcpy(cp + o, pattern + sel_start, | ||
123 | + sel_end - sel_start); | ||
124 | + o += sel_end - sel_start; | ||
125 | + } | ||
126 | + /* Remainder of pattern after closing brace */ | ||
127 | + if (tail_len > 0) { | ||
128 | + memcpy(cp + o, pattern + brace_end + 1, tail_len); | ||
129 | + o += tail_len; | ||
130 | + } | ||
131 | + cp[o] = '\0'; | ||
132 | + if (append(cp, patternsp, npatternsp) != 0) { | ||
133 | + free(cp); | ||
134 | + return -1; | ||
135 | + } | ||
136 | + return 0; | ||
137 | +} | ||
138 | + | ||
139 | +/* | ||
140 | + * Expand the first encountered brace in pattern, appending the expanded | ||
141 | + * patterns it yielded to the *patternsp array. | ||
142 | + * | ||
143 | + * Returns 0 on success or -1 on allocation failure. | ||
144 | + * | ||
145 | + * Signals whether expansion was performed via *expanded and whether | ||
146 | + * pattern was invalid via *invalid. | ||
147 | + */ | ||
148 | +static int | ||
149 | +brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp, | ||
150 | + int *expanded, int *invalid) | ||
151 | +{ | ||
152 | + int i; | ||
153 | + int in_bracket, brace_start, brace_end, brace_level; | ||
154 | + int sel_start, sel_end; | ||
155 | + | ||
156 | + *invalid = *expanded = 0; | ||
157 | + | ||
158 | + if (find_brace(pattern, &brace_start, &brace_end) != 0) { | ||
159 | + *invalid = 1; | ||
160 | + return 0; | ||
161 | + } else if (brace_start == -1) | ||
162 | + return 0; | ||
163 | + | ||
164 | + in_bracket = brace_level = 0; | ||
165 | + for (i = sel_start = brace_start + 1; i < brace_end; i++) { | ||
166 | + switch (pattern[i]) { | ||
167 | + case '{': | ||
168 | + if (in_bracket) | ||
169 | + break; | ||
170 | + brace_level++; | ||
171 | + break; | ||
172 | + case '}': | ||
173 | + if (in_bracket) | ||
174 | + break; | ||
175 | + brace_level--; | ||
176 | + break; | ||
177 | + case '[': | ||
178 | + in_bracket = 1; | ||
179 | + break; | ||
180 | + case ']': | ||
181 | + in_bracket = 0; | ||
182 | + break; | ||
183 | + case '\\': | ||
184 | + if (i < brace_end - 1) | ||
185 | + i++; /* skip */ | ||
186 | + break; | ||
187 | + } | ||
188 | + if (pattern[i] == ',' || i == brace_end - 1) { | ||
189 | + if (in_bracket || brace_level > 0) | ||
190 | + continue; | ||
191 | + /* End of a selection, emit an expanded pattern */ | ||
192 | + | ||
193 | + /* Adjust end index for last selection */ | ||
194 | + sel_end = (i == brace_end - 1) ? brace_end : i; | ||
195 | + if (emit_expansion(pattern, brace_start, brace_end, | ||
196 | + sel_start, sel_end, patternsp, npatternsp) != 0) | ||
197 | + return -1; | ||
198 | + /* move on to the next selection */ | ||
199 | + sel_start = i + 1; | ||
200 | + continue; | ||
201 | + } | ||
202 | + } | ||
203 | + if (in_bracket || brace_level > 0) { | ||
204 | + *invalid = 1; | ||
205 | + return 0; | ||
206 | + } | ||
207 | + /* success */ | ||
208 | + *expanded = 1; | ||
209 | + return 0; | ||
210 | +} | ||
211 | + | ||
212 | +/* Expand braces from pattern. Returns 0 on success, -1 on failure */ | ||
213 | +static int | ||
214 | +brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp) | ||
215 | +{ | ||
216 | + char *cp, *cp2, **active = NULL, **done = NULL; | ||
217 | + size_t i, nactive = 0, ndone = 0; | ||
218 | + int ret = -1, invalid = 0, expanded = 0; | ||
219 | + | ||
220 | + *patternsp = NULL; | ||
221 | + *npatternsp = 0; | ||
222 | + | ||
223 | + /* Start the worklist with the original pattern */ | ||
224 | + if ((cp = strdup(pattern)) == NULL) | ||
225 | + return -1; | ||
226 | + if (append(cp, &active, &nactive) != 0) { | ||
227 | + free(cp); | ||
228 | + return -1; | ||
229 | + } | ||
230 | + while (nactive > 0) { | ||
231 | + cp = active[nactive - 1]; | ||
232 | + nactive--; | ||
233 | + if (brace_expand_one(cp, &active, &nactive, | ||
234 | + &expanded, &invalid) == -1) { | ||
235 | + free(cp); | ||
236 | + goto fail; | ||
237 | + } | ||
238 | + if (invalid) | ||
239 | + fatal("%s: invalid brace pattern \"%s\"", __func__, cp); | ||
240 | + if (expanded) { | ||
241 | + /* | ||
242 | + * Current entry expanded to new entries on the | ||
243 | + * active list; discard the progenitor pattern. | ||
244 | + */ | ||
245 | + free(cp); | ||
246 | + continue; | ||
247 | + } | ||
248 | + /* | ||
249 | + * Pattern did not expand; append the finename component to | ||
250 | + * the completed list | ||
251 | + */ | ||
252 | + if ((cp2 = strrchr(cp, '/')) != NULL) | ||
253 | + *cp2++ = '\0'; | ||
254 | + else | ||
255 | + cp2 = cp; | ||
256 | + if (append(xstrdup(cp2), &done, &ndone) != 0) { | ||
257 | + free(cp); | ||
258 | + goto fail; | ||
259 | + } | ||
260 | + free(cp); | ||
261 | + } | ||
262 | + /* success */ | ||
263 | + *patternsp = done; | ||
264 | + *npatternsp = ndone; | ||
265 | + done = NULL; | ||
266 | + ndone = 0; | ||
267 | + ret = 0; | ||
268 | + fail: | ||
269 | + for (i = 0; i < nactive; i++) | ||
270 | + free(active[i]); | ||
271 | + free(active); | ||
272 | + for (i = 0; i < ndone; i++) | ||
273 | + free(done[i]); | ||
274 | + free(done); | ||
275 | + return ret; | ||
276 | +} | ||
277 | + | ||
278 | void | ||
279 | toremote(int argc, char **argv) | ||
280 | { | ||
281 | @@ -998,7 +1245,8 @@ sink(int argc, char **argv, const char *src) | ||
282 | unsigned long long ull; | ||
283 | int setimes, targisdir, wrerrno = 0; | ||
284 | char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; | ||
285 | - char *src_copy = NULL, *restrict_pattern = NULL; | ||
286 | + char **patterns = NULL; | ||
287 | + size_t n, npatterns = 0; | ||
288 | struct timeval tv[2]; | ||
289 | |||
290 | #define atime tv[0] | ||
291 | @@ -1028,16 +1276,13 @@ sink(int argc, char **argv, const char *src) | ||
292 | * Prepare to try to restrict incoming filenames to match | ||
293 | * the requested destination file glob. | ||
294 | */ | ||
295 | - if ((src_copy = strdup(src)) == NULL) | ||
296 | - fatal("strdup failed"); | ||
297 | - if ((restrict_pattern = strrchr(src_copy, '/')) != NULL) { | ||
298 | - *restrict_pattern++ = '\0'; | ||
299 | - } | ||
300 | + if (brace_expand(src, &patterns, &npatterns) != 0) | ||
301 | + fatal("%s: could not expand pattern", __func__); | ||
302 | } | ||
303 | for (first = 1;; first = 0) { | ||
304 | cp = buf; | ||
305 | if (atomicio(read, remin, cp, 1) != 1) | ||
306 | - return; | ||
307 | + goto done; | ||
308 | if (*cp++ == '\n') | ||
309 | SCREWUP("unexpected <newline>"); | ||
310 | do { | ||
311 | @@ -1063,7 +1308,7 @@ sink(int argc, char **argv, const char *src) | ||
312 | } | ||
313 | if (buf[0] == 'E') { | ||
314 | (void) atomicio(vwrite, remout, "", 1); | ||
315 | - return; | ||
316 | + goto done; | ||
317 | } | ||
318 | if (ch == '\n') | ||
319 | *--cp = 0; | ||
320 | @@ -1138,9 +1383,14 @@ sink(int argc, char **argv, const char *src) | ||
321 | run_err("error: unexpected filename: %s", cp); | ||
322 | exit(1); | ||
323 | } | ||
324 | - if (restrict_pattern != NULL && | ||
325 | - fnmatch(restrict_pattern, cp, 0) != 0) | ||
326 | - SCREWUP("filename does not match request"); | ||
327 | + if (npatterns > 0) { | ||
328 | + for (n = 0; n < npatterns; n++) { | ||
329 | + if (fnmatch(patterns[n], cp, 0) == 0) | ||
330 | + break; | ||
331 | + } | ||
332 | + if (n >= npatterns) | ||
333 | + SCREWUP("filename does not match request"); | ||
334 | + } | ||
335 | if (targisdir) { | ||
336 | static char *namebuf; | ||
337 | static size_t cursize; | ||
338 | @@ -1299,7 +1549,15 @@ bad: run_err("%s: %s", np, strerror(errno)); | ||
339 | break; | ||
340 | } | ||
341 | } | ||
342 | +done: | ||
343 | + for (n = 0; n < npatterns; n++) | ||
344 | + free(patterns[n]); | ||
345 | + free(patterns); | ||
346 | + return; | ||
347 | screwup: | ||
348 | + for (n = 0; n < npatterns; n++) | ||
349 | + free(patterns[n]); | ||
350 | + free(patterns); | ||
351 | run_err("protocol error: %s", why); | ||
352 | exit(1); | ||
353 | } | ||