From 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab Mon Sep 17 00:00:00 2001
|
|
From: "djm@openbsd.org" <djm@openbsd.org>
|
|
Date: Sun, 10 Feb 2019 11:15:52 +0000
|
|
Subject: 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
|
|
|
|
Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=3d896c157c722bc47adca51a58dca859225b5874
|
|
Bug-Debian: https://bugs.debian.org/923486
|
|
Last-Update: 2019-03-01
|
|
|
|
Patch-Name: scp-handle-braces.patch
|
|
---
|
|
scp.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
|
|
1 file changed, 269 insertions(+), 11 deletions(-)
|
|
|
|
diff --git a/scp.c b/scp.c
|
|
index 035037bcc..3888baab0 100644
|
|
--- a/scp.c
|
|
+++ b/scp.c
|
|
@@ -635,6 +635,253 @@ parse_scp_uri(const char *uri, char **userp, char **hostp, int *portp,
|
|
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)
|
|
{
|
|
@@ -998,7 +1245,8 @@ sink(int argc, char **argv, const char *src)
|
|
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]
|
|
@@ -1028,16 +1276,13 @@ sink(int argc, char **argv, const char *src)
|
|
* 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 <newline>");
|
|
do {
|
|
@@ -1063,7 +1308,7 @@ sink(int argc, char **argv, const char *src)
|
|
}
|
|
if (buf[0] == 'E') {
|
|
(void) atomicio(vwrite, remout, "", 1);
|
|
- return;
|
|
+ goto done;
|
|
}
|
|
if (ch == '\n')
|
|
*--cp = 0;
|
|
@@ -1138,9 +1383,14 @@ sink(int argc, char **argv, const char *src)
|
|
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;
|
|
@@ -1299,7 +1549,15 @@ bad: run_err("%s: %s", np, strerror(errno));
|
|
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);
|
|
}
|