Merge r345574 from vendor-crypto:
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 Discussed with: des Obtained from: OpenSSH-portable 3d896c157c722bc47adca51a58dca859225b5874
This commit is contained in:
commit
0967215db7
@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: scp.c,v 1.203 2019/01/27 07:14:11 jmc Exp $ */
|
||||
/* $OpenBSD: scp.c,v 1.204 2019/02/10 11:15:52 djm Exp $ */
|
||||
/*
|
||||
* scp - secure remote copy. This is basically patched BSD rcp which
|
||||
* uses ssh to do the data transfer (instead of using rcmd).
|
||||
@ -626,6 +626,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)
|
||||
{
|
||||
@ -989,7 +1236,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]
|
||||
@ -1019,16 +1267,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 {
|
||||
@ -1054,7 +1299,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;
|
||||
@ -1129,9 +1374,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;
|
||||
@ -1290,7 +1540,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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user