|
7 | 7 | #include "tag.h"
|
8 | 8 | #include "fsck.h"
|
9 | 9 | #include "refs.h"
|
| 10 | +#include "url.h" |
10 | 11 | #include "utf8.h"
|
11 | 12 | #include "sha1-array.h"
|
12 | 13 | #include "decorate.h"
|
@@ -946,17 +947,147 @@ static int fsck_tag(struct tag *tag, const char *data,
|
946 | 947 | return fsck_tag_buffer(tag, data, size, options);
|
947 | 948 | }
|
948 | 949 |
|
| 950 | +/* |
| 951 | + * Like builtin/submodule--helper.c's starts_with_dot_slash, but without |
| 952 | + * relying on the platform-dependent is_dir_sep helper. |
| 953 | + * |
| 954 | + * This is for use in checking whether a submodule URL is interpreted as |
| 955 | + * relative to the current directory on any platform, since \ is a |
| 956 | + * directory separator on Windows but not on other platforms. |
| 957 | + */ |
| 958 | +static int starts_with_dot_slash(const char *str) |
| 959 | +{ |
| 960 | + return str[0] == '.' && (str[1] == '/' || str[1] == '\\'); |
| 961 | +} |
| 962 | + |
| 963 | +/* |
| 964 | + * Like starts_with_dot_slash, this is a variant of submodule--helper's |
| 965 | + * helper of the same name with the twist that it accepts backslash as a |
| 966 | + * directory separator even on non-Windows platforms. |
| 967 | + */ |
| 968 | +static int starts_with_dot_dot_slash(const char *str) |
| 969 | +{ |
| 970 | + return str[0] == '.' && starts_with_dot_slash(str + 1); |
| 971 | +} |
| 972 | + |
| 973 | +static int submodule_url_is_relative(const char *url) |
| 974 | +{ |
| 975 | + return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url); |
| 976 | +} |
| 977 | + |
| 978 | +/* |
| 979 | + * Count directory components that a relative submodule URL should chop |
| 980 | + * from the remote_url it is to be resolved against. |
| 981 | + * |
| 982 | + * In other words, this counts "../" components at the start of a |
| 983 | + * submodule URL. |
| 984 | + * |
| 985 | + * Returns the number of directory components to chop and writes a |
| 986 | + * pointer to the next character of url after all leading "./" and |
| 987 | + * "../" components to out. |
| 988 | + */ |
| 989 | +static int count_leading_dotdots(const char *url, const char **out) |
| 990 | +{ |
| 991 | + int result = 0; |
| 992 | + while (1) { |
| 993 | + if (starts_with_dot_dot_slash(url)) { |
| 994 | + result++; |
| 995 | + url += strlen("../"); |
| 996 | + continue; |
| 997 | + } |
| 998 | + if (starts_with_dot_slash(url)) { |
| 999 | + url += strlen("./"); |
| 1000 | + continue; |
| 1001 | + } |
| 1002 | + *out = url; |
| 1003 | + return result; |
| 1004 | + } |
| 1005 | +} |
| 1006 | +/* |
| 1007 | + * Check whether a transport is implemented by git-remote-curl. |
| 1008 | + * |
| 1009 | + * If it is, returns 1 and writes the URL that would be passed to |
| 1010 | + * git-remote-curl to the "out" parameter. |
| 1011 | + * |
| 1012 | + * Otherwise, returns 0 and leaves "out" untouched. |
| 1013 | + * |
| 1014 | + * Examples: |
| 1015 | + * http::https://example.com/repo.git -> 1, https://example.com/repo.git |
| 1016 | + * https://example.com/repo.git -> 1, https://example.com/repo.git |
| 1017 | + * git://example.com/repo.git -> 0 |
| 1018 | + * |
| 1019 | + * This is for use in checking for previously exploitable bugs that |
| 1020 | + * required a submodule URL to be passed to git-remote-curl. |
| 1021 | + */ |
| 1022 | +static int url_to_curl_url(const char *url, const char **out) |
| 1023 | +{ |
| 1024 | + /* |
| 1025 | + * We don't need to check for case-aliases, "http.exe", and so |
| 1026 | + * on because in the default configuration, is_transport_allowed |
| 1027 | + * prevents URLs with those schemes from being cloned |
| 1028 | + * automatically. |
| 1029 | + */ |
| 1030 | + if (skip_prefix(url, "http::", out) || |
| 1031 | + skip_prefix(url, "https::", out) || |
| 1032 | + skip_prefix(url, "ftp::", out) || |
| 1033 | + skip_prefix(url, "ftps::", out)) |
| 1034 | + return 1; |
| 1035 | + if (starts_with(url, "http://") || |
| 1036 | + starts_with(url, "https://") || |
| 1037 | + starts_with(url, "ftp://") || |
| 1038 | + starts_with(url, "ftps://")) { |
| 1039 | + *out = url; |
| 1040 | + return 1; |
| 1041 | + } |
| 1042 | + return 0; |
| 1043 | +} |
| 1044 | + |
949 | 1045 | static int check_submodule_url(const char *url)
|
950 | 1046 | {
|
951 |
| - struct credential c = CREDENTIAL_INIT; |
952 |
| - int ret; |
| 1047 | + const char *curl_url; |
953 | 1048 |
|
954 | 1049 | if (looks_like_command_line_option(url))
|
955 | 1050 | return -1;
|
956 | 1051 |
|
957 |
| - ret = credential_from_url_gently(&c, url, 1); |
958 |
| - credential_clear(&c); |
959 |
| - return ret; |
| 1052 | + if (submodule_url_is_relative(url)) { |
| 1053 | + char *decoded; |
| 1054 | + const char *next; |
| 1055 | + int has_nl; |
| 1056 | + |
| 1057 | + /* |
| 1058 | + * This could be appended to an http URL and url-decoded; |
| 1059 | + * check for malicious characters. |
| 1060 | + */ |
| 1061 | + decoded = url_decode(url); |
| 1062 | + has_nl = !!strchr(decoded, '\n'); |
| 1063 | + |
| 1064 | + free(decoded); |
| 1065 | + if (has_nl) |
| 1066 | + return -1; |
| 1067 | + |
| 1068 | + /* |
| 1069 | + * URLs which escape their root via "../" can overwrite |
| 1070 | + * the host field and previous components, resolving to |
| 1071 | + * URLs like https::example.com/submodule.git and |
| 1072 | + * https:///example.com/submodule.git that were |
| 1073 | + * susceptible to CVE-2020-11008. |
| 1074 | + */ |
| 1075 | + if (count_leading_dotdots(url, &next) > 0 && |
| 1076 | + (*next == ':' || *next == '/')) |
| 1077 | + return -1; |
| 1078 | + } |
| 1079 | + |
| 1080 | + else if (url_to_curl_url(url, &curl_url)) { |
| 1081 | + struct credential c = CREDENTIAL_INIT; |
| 1082 | + int ret = 0; |
| 1083 | + if (credential_from_url_gently(&c, curl_url, 1) || |
| 1084 | + !*c.host) |
| 1085 | + ret = -1; |
| 1086 | + credential_clear(&c); |
| 1087 | + return ret; |
| 1088 | + } |
| 1089 | + |
| 1090 | + return 0; |
960 | 1091 | }
|
961 | 1092 |
|
962 | 1093 | struct fsck_gitmodules_data {
|
|
0 commit comments