diff --git a/openssh-7.9p1-brace-expansion.patch b/openssh-7.9p1-brace-expansion.patch new file mode 100644 index 0000000..ab0aa32 --- /dev/null +++ b/openssh-7.9p1-brace-expansion.patch @@ -0,0 +1,348 @@ +From 3d896c157c722bc47adca51a58dca859225b5874 Mon Sep 17 00:00:00 2001 +From: "djm@openbsd.org" +Date: Sun, 10 Feb 2019 11:15:52 +0000 +Subject: [PATCH] upstream: when checking that filenames sent by the server + side + +match what the client requested, be prepared to handle shell-style brace +alternations, e.g. "{foo,bar}". + +"looks good to me" millert@ + in snaps for the last week courtesy +deraadt@ + +OpenBSD-Commit-ID: 3b1ce7639b0b25b2248e3a30f561a548f6815f3e +--- + scp.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 270 insertions(+), 12 deletions(-) + +Index: openssh-7.9p1/scp.c +=================================================================== +--- openssh-7.9p1.orig/scp.c ++++ openssh-7.9p1/scp.c +@@ -627,6 +627,253 @@ parse_scp_uri(const char *uri, char **us + return r; + } + ++/* Appends a string to an array; returns 0 on success, -1 on alloc failure */ ++static int ++append(char *cp, char ***ap, size_t *np) ++{ ++ char **tmp; ++ ++ if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL) ++ return -1; ++ tmp[(*np)] = cp; ++ (*np)++; ++ *ap = tmp; ++ return 0; ++} ++ ++/* ++ * Finds the start and end of the first brace pair in the pattern. ++ * returns 0 on success or -1 for invalid patterns. ++ */ ++static int ++find_brace(const char *pattern, int *startp, int *endp) ++{ ++ int i; ++ int in_bracket, brace_level; ++ ++ *startp = *endp = -1; ++ in_bracket = brace_level = 0; ++ for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) { ++ switch (pattern[i]) { ++ case '\\': ++ /* skip next character */ ++ if (pattern[i + 1] != '\0') ++ i++; ++ break; ++ case '[': ++ in_bracket = 1; ++ break; ++ case ']': ++ in_bracket = 0; ++ break; ++ case '{': ++ if (in_bracket) ++ break; ++ if (pattern[i + 1] == '}') { ++ /* Protect a single {}, for find(1), like csh */ ++ i++; /* skip */ ++ break; ++ } ++ if (*startp == -1) ++ *startp = i; ++ brace_level++; ++ break; ++ case '}': ++ if (in_bracket) ++ break; ++ if (*startp < 0) { ++ /* Unbalanced brace */ ++ return -1; ++ } ++ if (--brace_level <= 0) ++ *endp = i; ++ break; ++ } ++ } ++ /* unbalanced brackets/braces */ ++ if (*endp < 0 && (*startp >= 0 || in_bracket)) ++ return -1; ++ return 0; ++} ++ ++/* ++ * Assembles and records a successfully-expanded pattern, returns -1 on ++ * alloc failure. ++ */ ++static int ++emit_expansion(const char *pattern, int brace_start, int brace_end, ++ int sel_start, int sel_end, char ***patternsp, size_t *npatternsp) ++{ ++ char *cp; ++ int o = 0, tail_len = strlen(pattern + brace_end + 1); ++ ++ if ((cp = malloc(brace_start + (sel_end - sel_start) + ++ tail_len + 1)) == NULL) ++ return -1; ++ ++ /* Pattern before initial brace */ ++ if (brace_start > 0) { ++ memcpy(cp, pattern, brace_start); ++ o = brace_start; ++ } ++ /* Current braced selection */ ++ if (sel_end - sel_start > 0) { ++ memcpy(cp + o, pattern + sel_start, ++ sel_end - sel_start); ++ o += sel_end - sel_start; ++ } ++ /* Remainder of pattern after closing brace */ ++ if (tail_len > 0) { ++ memcpy(cp + o, pattern + brace_end + 1, tail_len); ++ o += tail_len; ++ } ++ cp[o] = '\0'; ++ if (append(cp, patternsp, npatternsp) != 0) { ++ free(cp); ++ return -1; ++ } ++ return 0; ++} ++ ++/* ++ * Expand the first encountered brace in pattern, appending the expanded ++ * patterns it yielded to the *patternsp array. ++ * ++ * Returns 0 on success or -1 on allocation failure. ++ * ++ * Signals whether expansion was performed via *expanded and whether ++ * pattern was invalid via *invalid. ++ */ ++static int ++brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp, ++ int *expanded, int *invalid) ++{ ++ int i; ++ int in_bracket, brace_start, brace_end, brace_level; ++ int sel_start, sel_end; ++ ++ *invalid = *expanded = 0; ++ ++ if (find_brace(pattern, &brace_start, &brace_end) != 0) { ++ *invalid = 1; ++ return 0; ++ } else if (brace_start == -1) ++ return 0; ++ ++ in_bracket = brace_level = 0; ++ for (i = sel_start = brace_start + 1; i < brace_end; i++) { ++ switch (pattern[i]) { ++ case '{': ++ if (in_bracket) ++ break; ++ brace_level++; ++ break; ++ case '}': ++ if (in_bracket) ++ break; ++ brace_level--; ++ break; ++ case '[': ++ in_bracket = 1; ++ break; ++ case ']': ++ in_bracket = 0; ++ break; ++ case '\\': ++ if (i < brace_end - 1) ++ i++; /* skip */ ++ break; ++ } ++ if (pattern[i] == ',' || i == brace_end - 1) { ++ if (in_bracket || brace_level > 0) ++ continue; ++ /* End of a selection, emit an expanded pattern */ ++ ++ /* Adjust end index for last selection */ ++ sel_end = (i == brace_end - 1) ? brace_end : i; ++ if (emit_expansion(pattern, brace_start, brace_end, ++ sel_start, sel_end, patternsp, npatternsp) != 0) ++ return -1; ++ /* move on to the next selection */ ++ sel_start = i + 1; ++ continue; ++ } ++ } ++ if (in_bracket || brace_level > 0) { ++ *invalid = 1; ++ return 0; ++ } ++ /* success */ ++ *expanded = 1; ++ return 0; ++} ++ ++/* Expand braces from pattern. Returns 0 on success, -1 on failure */ ++static int ++brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp) ++{ ++ char *cp, *cp2, **active = NULL, **done = NULL; ++ size_t i, nactive = 0, ndone = 0; ++ int ret = -1, invalid = 0, expanded = 0; ++ ++ *patternsp = NULL; ++ *npatternsp = 0; ++ ++ /* Start the worklist with the original pattern */ ++ if ((cp = strdup(pattern)) == NULL) ++ return -1; ++ if (append(cp, &active, &nactive) != 0) { ++ free(cp); ++ return -1; ++ } ++ while (nactive > 0) { ++ cp = active[nactive - 1]; ++ nactive--; ++ if (brace_expand_one(cp, &active, &nactive, ++ &expanded, &invalid) == -1) { ++ free(cp); ++ goto fail; ++ } ++ if (invalid) ++ fatal("%s: invalid brace pattern \"%s\"", __func__, cp); ++ if (expanded) { ++ /* ++ * Current entry expanded to new entries on the ++ * active list; discard the progenitor pattern. ++ */ ++ free(cp); ++ continue; ++ } ++ /* ++ * Pattern did not expand; append the finename component to ++ * the completed list ++ */ ++ if ((cp2 = strrchr(cp, '/')) != NULL) ++ *cp2++ = '\0'; ++ else ++ cp2 = cp; ++ if (append(xstrdup(cp2), &done, &ndone) != 0) { ++ free(cp); ++ goto fail; ++ } ++ free(cp); ++ } ++ /* success */ ++ *patternsp = done; ++ *npatternsp = ndone; ++ done = NULL; ++ ndone = 0; ++ ret = 0; ++ fail: ++ for (i = 0; i < nactive; i++) ++ free(active[i]); ++ free(active); ++ for (i = 0; i < ndone; i++) ++ free(done[i]); ++ free(done); ++ return ret; ++} ++ + void + toremote(int argc, char **argv) + { +@@ -990,7 +1237,8 @@ sink(int argc, char **argv, const char * + unsigned long long ull; + int setimes, targisdir, wrerrno = 0; + char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; +- char *src_copy = NULL, *restrict_pattern = NULL; ++ char **patterns = NULL; ++ size_t n, npatterns = 0; + struct timeval tv[2]; + + #define atime tv[0] +@@ -1020,16 +1268,13 @@ sink(int argc, char **argv, const char * + * Prepare to try to restrict incoming filenames to match + * the requested destination file glob. + */ +- if ((src_copy = strdup(src)) == NULL) +- fatal("strdup failed"); +- if ((restrict_pattern = strrchr(src_copy, '/')) != NULL) { +- *restrict_pattern++ = '\0'; +- } ++ if (brace_expand(src, &patterns, &npatterns) != 0) ++ fatal("%s: could not expand pattern", __func__); + } + for (first = 1;; first = 0) { + cp = buf; + if (atomicio(read, remin, cp, 1) != 1) +- return; ++ goto done; + if (*cp++ == '\n') + SCREWUP("unexpected "); + do { +@@ -1055,7 +1300,7 @@ sink(int argc, char **argv, const char * + } + if (buf[0] == 'E') { + (void) atomicio(vwrite, remout, "", 1); +- return; ++ goto done; + } + if (ch == '\n') + *--cp = 0; +@@ -1130,9 +1375,14 @@ sink(int argc, char **argv, const char * + run_err("error: unexpected filename: %s", cp); + exit(1); + } +- if (restrict_pattern != NULL && +- fnmatch(restrict_pattern, cp, 0) != 0) +- SCREWUP("filename does not match request"); ++ if (npatterns > 0) { ++ for (n = 0; n < npatterns; n++) { ++ if (fnmatch(patterns[n], cp, 0) == 0) ++ break; ++ } ++ if (n >= npatterns) ++ SCREWUP("filename does not match request"); ++ } + if (targisdir) { + static char *namebuf; + static size_t cursize; +@@ -1291,7 +1541,15 @@ bad: run_err("%s: %s", np, strerror(er + break; + } + } ++done: ++ for (n = 0; n < npatterns; n++) ++ free(patterns[n]); ++ free(patterns); ++ return; + screwup: ++ for (n = 0; n < npatterns; n++) ++ free(patterns[n]); ++ free(patterns); + run_err("protocol error: %s", why); + exit(1); + } diff --git a/openssh.changes b/openssh.changes index 427b358..51d3546 100644 --- a/openssh.changes +++ b/openssh.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Feb 18 10:01:45 UTC 2019 - Pedro Monreal Gonzalez + +- Handle brace expansion in scp when checking that filenames sent + by the server side match what the client requested [bsc#1125687] + ------------------------------------------------------------------- Thu Feb 14 15:27:53 UTC 2019 - Pedro Monreal Gonzalez diff --git a/openssh.spec b/openssh.spec index 8720e4a..59e28d8 100644 --- a/openssh.spec +++ b/openssh.spec @@ -104,6 +104,7 @@ Patch35: openssh-7.9p1-CVE-2018-20685.patch Patch36: openssh-CVE-2019-6109-sanitize-scp-filenames.patch Patch37: openssh-CVE-2019-6109-force-progressmeter-update.patch Patch38: openssh-CVE-2019-6111-scp-client-wildcard.patch +Patch39: openssh-7.9p1-brace-expansion.patch BuildRequires: audit-devel BuildRequires: autoconf BuildRequires: groff