diff options
author | djm@openbsd.org <djm@openbsd.org> | 2019-02-10 11:15:52 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2019-03-01 09:56:14 +0000 |
commit | 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab (patch) | |
tree | be44e8b1f89b2b8880f25add29fb49ed1dde4eb3 | |
parent | d94226d4fcefbc398c5583e12b5d07ca33884ba4 (diff) |
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
-rw-r--r-- | scp.c | 280 |
1 files changed, 269 insertions, 11 deletions
@@ -635,6 +635,253 @@ parse_scp_uri(const char *uri, char **userp, char **hostp, int *portp, | |||
635 | return r; | 635 | return r; |
636 | } | 636 | } |
637 | 637 | ||
638 | /* Appends a string to an array; returns 0 on success, -1 on alloc failure */ | ||
639 | static int | ||
640 | append(char *cp, char ***ap, size_t *np) | ||
641 | { | ||
642 | char **tmp; | ||
643 | |||
644 | if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL) | ||
645 | return -1; | ||
646 | tmp[(*np)] = cp; | ||
647 | (*np)++; | ||
648 | *ap = tmp; | ||
649 | return 0; | ||
650 | } | ||
651 | |||
652 | /* | ||
653 | * Finds the start and end of the first brace pair in the pattern. | ||
654 | * returns 0 on success or -1 for invalid patterns. | ||
655 | */ | ||
656 | static int | ||
657 | find_brace(const char *pattern, int *startp, int *endp) | ||
658 | { | ||
659 | int i; | ||
660 | int in_bracket, brace_level; | ||
661 | |||
662 | *startp = *endp = -1; | ||
663 | in_bracket = brace_level = 0; | ||
664 | for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) { | ||
665 | switch (pattern[i]) { | ||
666 | case '\\': | ||
667 | /* skip next character */ | ||
668 | if (pattern[i + 1] != '\0') | ||
669 | i++; | ||
670 | break; | ||
671 | case '[': | ||
672 | in_bracket = 1; | ||
673 | break; | ||
674 | case ']': | ||
675 | in_bracket = 0; | ||
676 | break; | ||
677 | case '{': | ||
678 | if (in_bracket) | ||
679 | break; | ||
680 | if (pattern[i + 1] == '}') { | ||
681 | /* Protect a single {}, for find(1), like csh */ | ||
682 | i++; /* skip */ | ||
683 | break; | ||
684 | } | ||
685 | if (*startp == -1) | ||
686 | *startp = i; | ||
687 | brace_level++; | ||
688 | break; | ||
689 | case '}': | ||
690 | if (in_bracket) | ||
691 | break; | ||
692 | if (*startp < 0) { | ||
693 | /* Unbalanced brace */ | ||
694 | return -1; | ||
695 | } | ||
696 | if (--brace_level <= 0) | ||
697 | *endp = i; | ||
698 | break; | ||
699 | } | ||
700 | } | ||
701 | /* unbalanced brackets/braces */ | ||
702 | if (*endp < 0 && (*startp >= 0 || in_bracket)) | ||
703 | return -1; | ||
704 | return 0; | ||
705 | } | ||
706 | |||
707 | /* | ||
708 | * Assembles and records a successfully-expanded pattern, returns -1 on | ||
709 | * alloc failure. | ||
710 | */ | ||
711 | static int | ||
712 | emit_expansion(const char *pattern, int brace_start, int brace_end, | ||
713 | int sel_start, int sel_end, char ***patternsp, size_t *npatternsp) | ||
714 | { | ||
715 | char *cp; | ||
716 | int o = 0, tail_len = strlen(pattern + brace_end + 1); | ||
717 | |||
718 | if ((cp = malloc(brace_start + (sel_end - sel_start) + | ||
719 | tail_len + 1)) == NULL) | ||
720 | return -1; | ||
721 | |||
722 | /* Pattern before initial brace */ | ||
723 | if (brace_start > 0) { | ||
724 | memcpy(cp, pattern, brace_start); | ||
725 | o = brace_start; | ||
726 | } | ||
727 | /* Current braced selection */ | ||
728 | if (sel_end - sel_start > 0) { | ||
729 | memcpy(cp + o, pattern + sel_start, | ||
730 | sel_end - sel_start); | ||
731 | o += sel_end - sel_start; | ||
732 | } | ||
733 | /* Remainder of pattern after closing brace */ | ||
734 | if (tail_len > 0) { | ||
735 | memcpy(cp + o, pattern + brace_end + 1, tail_len); | ||
736 | o += tail_len; | ||
737 | } | ||
738 | cp[o] = '\0'; | ||
739 | if (append(cp, patternsp, npatternsp) != 0) { | ||
740 | free(cp); | ||
741 | return -1; | ||
742 | } | ||
743 | return 0; | ||
744 | } | ||
745 | |||
746 | /* | ||
747 | * Expand the first encountered brace in pattern, appending the expanded | ||
748 | * patterns it yielded to the *patternsp array. | ||
749 | * | ||
750 | * Returns 0 on success or -1 on allocation failure. | ||
751 | * | ||
752 | * Signals whether expansion was performed via *expanded and whether | ||
753 | * pattern was invalid via *invalid. | ||
754 | */ | ||
755 | static int | ||
756 | brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp, | ||
757 | int *expanded, int *invalid) | ||
758 | { | ||
759 | int i; | ||
760 | int in_bracket, brace_start, brace_end, brace_level; | ||
761 | int sel_start, sel_end; | ||
762 | |||
763 | *invalid = *expanded = 0; | ||
764 | |||
765 | if (find_brace(pattern, &brace_start, &brace_end) != 0) { | ||
766 | *invalid = 1; | ||
767 | return 0; | ||
768 | } else if (brace_start == -1) | ||
769 | return 0; | ||
770 | |||
771 | in_bracket = brace_level = 0; | ||
772 | for (i = sel_start = brace_start + 1; i < brace_end; i++) { | ||
773 | switch (pattern[i]) { | ||
774 | case '{': | ||
775 | if (in_bracket) | ||
776 | break; | ||
777 | brace_level++; | ||
778 | break; | ||
779 | case '}': | ||
780 | if (in_bracket) | ||
781 | break; | ||
782 | brace_level--; | ||
783 | break; | ||
784 | case '[': | ||
785 | in_bracket = 1; | ||
786 | break; | ||
787 | case ']': | ||
788 | in_bracket = 0; | ||
789 | break; | ||
790 | case '\\': | ||
791 | if (i < brace_end - 1) | ||
792 | i++; /* skip */ | ||
793 | break; | ||
794 | } | ||
795 | if (pattern[i] == ',' || i == brace_end - 1) { | ||
796 | if (in_bracket || brace_level > 0) | ||
797 | continue; | ||
798 | /* End of a selection, emit an expanded pattern */ | ||
799 | |||
800 | /* Adjust end index for last selection */ | ||
801 | sel_end = (i == brace_end - 1) ? brace_end : i; | ||
802 | if (emit_expansion(pattern, brace_start, brace_end, | ||
803 | sel_start, sel_end, patternsp, npatternsp) != 0) | ||
804 | return -1; | ||
805 | /* move on to the next selection */ | ||
806 | sel_start = i + 1; | ||
807 | continue; | ||
808 | } | ||
809 | } | ||
810 | if (in_bracket || brace_level > 0) { | ||
811 | *invalid = 1; | ||
812 | return 0; | ||
813 | } | ||
814 | /* success */ | ||
815 | *expanded = 1; | ||
816 | return 0; | ||
817 | } | ||
818 | |||
819 | /* Expand braces from pattern. Returns 0 on success, -1 on failure */ | ||
820 | static int | ||
821 | brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp) | ||
822 | { | ||
823 | char *cp, *cp2, **active = NULL, **done = NULL; | ||
824 | size_t i, nactive = 0, ndone = 0; | ||
825 | int ret = -1, invalid = 0, expanded = 0; | ||
826 | |||
827 | *patternsp = NULL; | ||
828 | *npatternsp = 0; | ||
829 | |||
830 | /* Start the worklist with the original pattern */ | ||
831 | if ((cp = strdup(pattern)) == NULL) | ||
832 | return -1; | ||
833 | if (append(cp, &active, &nactive) != 0) { | ||
834 | free(cp); | ||
835 | return -1; | ||
836 | } | ||
837 | while (nactive > 0) { | ||
838 | cp = active[nactive - 1]; | ||
839 | nactive--; | ||
840 | if (brace_expand_one(cp, &active, &nactive, | ||
841 | &expanded, &invalid) == -1) { | ||
842 | free(cp); | ||
843 | goto fail; | ||
844 | } | ||
845 | if (invalid) | ||
846 | fatal("%s: invalid brace pattern \"%s\"", __func__, cp); | ||
847 | if (expanded) { | ||
848 | /* | ||
849 | * Current entry expanded to new entries on the | ||
850 | * active list; discard the progenitor pattern. | ||
851 | */ | ||
852 | free(cp); | ||
853 | continue; | ||
854 | } | ||
855 | /* | ||
856 | * Pattern did not expand; append the finename component to | ||
857 | * the completed list | ||
858 | */ | ||
859 | if ((cp2 = strrchr(cp, '/')) != NULL) | ||
860 | *cp2++ = '\0'; | ||
861 | else | ||
862 | cp2 = cp; | ||
863 | if (append(xstrdup(cp2), &done, &ndone) != 0) { | ||
864 | free(cp); | ||
865 | goto fail; | ||
866 | } | ||
867 | free(cp); | ||
868 | } | ||
869 | /* success */ | ||
870 | *patternsp = done; | ||
871 | *npatternsp = ndone; | ||
872 | done = NULL; | ||
873 | ndone = 0; | ||
874 | ret = 0; | ||
875 | fail: | ||
876 | for (i = 0; i < nactive; i++) | ||
877 | free(active[i]); | ||
878 | free(active); | ||
879 | for (i = 0; i < ndone; i++) | ||
880 | free(done[i]); | ||
881 | free(done); | ||
882 | return ret; | ||
883 | } | ||
884 | |||
638 | void | 885 | void |
639 | toremote(int argc, char **argv) | 886 | toremote(int argc, char **argv) |
640 | { | 887 | { |
@@ -998,7 +1245,8 @@ sink(int argc, char **argv, const char *src) | |||
998 | unsigned long long ull; | 1245 | unsigned long long ull; |
999 | int setimes, targisdir, wrerrno = 0; | 1246 | int setimes, targisdir, wrerrno = 0; |
1000 | char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; | 1247 | char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; |
1001 | char *src_copy = NULL, *restrict_pattern = NULL; | 1248 | char **patterns = NULL; |
1249 | size_t n, npatterns = 0; | ||
1002 | struct timeval tv[2]; | 1250 | struct timeval tv[2]; |
1003 | 1251 | ||
1004 | #define atime tv[0] | 1252 | #define atime tv[0] |
@@ -1028,16 +1276,13 @@ sink(int argc, char **argv, const char *src) | |||
1028 | * Prepare to try to restrict incoming filenames to match | 1276 | * Prepare to try to restrict incoming filenames to match |
1029 | * the requested destination file glob. | 1277 | * the requested destination file glob. |
1030 | */ | 1278 | */ |
1031 | if ((src_copy = strdup(src)) == NULL) | 1279 | if (brace_expand(src, &patterns, &npatterns) != 0) |
1032 | fatal("strdup failed"); | 1280 | fatal("%s: could not expand pattern", __func__); |
1033 | if ((restrict_pattern = strrchr(src_copy, '/')) != NULL) { | ||
1034 | *restrict_pattern++ = '\0'; | ||
1035 | } | ||
1036 | } | 1281 | } |
1037 | for (first = 1;; first = 0) { | 1282 | for (first = 1;; first = 0) { |
1038 | cp = buf; | 1283 | cp = buf; |
1039 | if (atomicio(read, remin, cp, 1) != 1) | 1284 | if (atomicio(read, remin, cp, 1) != 1) |
1040 | return; | 1285 | goto done; |
1041 | if (*cp++ == '\n') | 1286 | if (*cp++ == '\n') |
1042 | SCREWUP("unexpected <newline>"); | 1287 | SCREWUP("unexpected <newline>"); |
1043 | do { | 1288 | do { |
@@ -1063,7 +1308,7 @@ sink(int argc, char **argv, const char *src) | |||
1063 | } | 1308 | } |
1064 | if (buf[0] == 'E') { | 1309 | if (buf[0] == 'E') { |
1065 | (void) atomicio(vwrite, remout, "", 1); | 1310 | (void) atomicio(vwrite, remout, "", 1); |
1066 | return; | 1311 | goto done; |
1067 | } | 1312 | } |
1068 | if (ch == '\n') | 1313 | if (ch == '\n') |
1069 | *--cp = 0; | 1314 | *--cp = 0; |
@@ -1138,9 +1383,14 @@ sink(int argc, char **argv, const char *src) | |||
1138 | run_err("error: unexpected filename: %s", cp); | 1383 | run_err("error: unexpected filename: %s", cp); |
1139 | exit(1); | 1384 | exit(1); |
1140 | } | 1385 | } |
1141 | if (restrict_pattern != NULL && | 1386 | if (npatterns > 0) { |
1142 | fnmatch(restrict_pattern, cp, 0) != 0) | 1387 | for (n = 0; n < npatterns; n++) { |
1143 | SCREWUP("filename does not match request"); | 1388 | if (fnmatch(patterns[n], cp, 0) == 0) |
1389 | break; | ||
1390 | } | ||
1391 | if (n >= npatterns) | ||
1392 | SCREWUP("filename does not match request"); | ||
1393 | } | ||
1144 | if (targisdir) { | 1394 | if (targisdir) { |
1145 | static char *namebuf; | 1395 | static char *namebuf; |
1146 | static size_t cursize; | 1396 | static size_t cursize; |
@@ -1299,7 +1549,15 @@ bad: run_err("%s: %s", np, strerror(errno)); | |||
1299 | break; | 1549 | break; |
1300 | } | 1550 | } |
1301 | } | 1551 | } |
1552 | done: | ||
1553 | for (n = 0; n < npatterns; n++) | ||
1554 | free(patterns[n]); | ||
1555 | free(patterns); | ||
1556 | return; | ||
1302 | screwup: | 1557 | screwup: |
1558 | for (n = 0; n < npatterns; n++) | ||
1559 | free(patterns[n]); | ||
1560 | free(patterns); | ||
1303 | run_err("protocol error: %s", why); | 1561 | run_err("protocol error: %s", why); |
1304 | exit(1); | 1562 | exit(1); |
1305 | } | 1563 | } |