|
9 | 9 | #include "tag.h"
|
10 | 10 | #include "fsck.h"
|
11 | 11 | #include "refs.h"
|
| 12 | +#include "url.h" |
12 | 13 | #include "utf8.h"
|
13 | 14 | #include "sha1-array.h"
|
14 | 15 | #include "decorate.h"
|
@@ -989,17 +990,147 @@ static int fsck_tag(struct tag *tag, const char *data,
|
989 | 990 | return fsck_tag_buffer(tag, data, size, options);
|
990 | 991 | }
|
991 | 992 |
|
| 993 | +/* |
| 994 | + * Like builtin/submodule--helper.c's starts_with_dot_slash, but without |
| 995 | + * relying on the platform-dependent is_dir_sep helper. |
| 996 | + * |
| 997 | + * This is for use in checking whether a submodule URL is interpreted as |
| 998 | + * relative to the current directory on any platform, since \ is a |
| 999 | + * directory separator on Windows but not on other platforms. |
| 1000 | + */ |
| 1001 | +static int starts_with_dot_slash(const char *str) |
| 1002 | +{ |
| 1003 | + return str[0] == '.' && (str[1] == '/' || str[1] == '\\'); |
| 1004 | +} |
| 1005 | + |
| 1006 | +/* |
| 1007 | + * Like starts_with_dot_slash, this is a variant of submodule--helper's |
| 1008 | + * helper of the same name with the twist that it accepts backslash as a |
| 1009 | + * directory separator even on non-Windows platforms. |
| 1010 | + */ |
| 1011 | +static int starts_with_dot_dot_slash(const char *str) |
| 1012 | +{ |
| 1013 | + return str[0] == '.' && starts_with_dot_slash(str + 1); |
| 1014 | +} |
| 1015 | + |
| 1016 | +static int submodule_url_is_relative(const char *url) |
| 1017 | +{ |
| 1018 | + return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url); |
| 1019 | +} |
| 1020 | + |
| 1021 | +/* |
| 1022 | + * Count directory components that a relative submodule URL should chop |
| 1023 | + * from the remote_url it is to be resolved against. |
| 1024 | + * |
| 1025 | + * In other words, this counts "../" components at the start of a |
| 1026 | + * submodule URL. |
| 1027 | + * |
| 1028 | + * Returns the number of directory components to chop and writes a |
| 1029 | + * pointer to the next character of url after all leading "./" and |
| 1030 | + * "../" components to out. |
| 1031 | + */ |
| 1032 | +static int count_leading_dotdots(const char *url, const char **out) |
| 1033 | +{ |
| 1034 | + int result = 0; |
| 1035 | + while (1) { |
| 1036 | + if (starts_with_dot_dot_slash(url)) { |
| 1037 | + result++; |
| 1038 | + url += strlen("../"); |
| 1039 | + continue; |
| 1040 | + } |
| 1041 | + if (starts_with_dot_slash(url)) { |
| 1042 | + url += strlen("./"); |
| 1043 | + continue; |
| 1044 | + } |
| 1045 | + *out = url; |
| 1046 | + return result; |
| 1047 | + } |
| 1048 | +} |
| 1049 | +/* |
| 1050 | + * Check whether a transport is implemented by git-remote-curl. |
| 1051 | + * |
| 1052 | + * If it is, returns 1 and writes the URL that would be passed to |
| 1053 | + * git-remote-curl to the "out" parameter. |
| 1054 | + * |
| 1055 | + * Otherwise, returns 0 and leaves "out" untouched. |
| 1056 | + * |
| 1057 | + * Examples: |
| 1058 | + * http::https://example.com/repo.git -> 1, https://example.com/repo.git |
| 1059 | + * https://example.com/repo.git -> 1, https://example.com/repo.git |
| 1060 | + * git://example.com/repo.git -> 0 |
| 1061 | + * |
| 1062 | + * This is for use in checking for previously exploitable bugs that |
| 1063 | + * required a submodule URL to be passed to git-remote-curl. |
| 1064 | + */ |
| 1065 | +static int url_to_curl_url(const char *url, const char **out) |
| 1066 | +{ |
| 1067 | + /* |
| 1068 | + * We don't need to check for case-aliases, "http.exe", and so |
| 1069 | + * on because in the default configuration, is_transport_allowed |
| 1070 | + * prevents URLs with those schemes from being cloned |
| 1071 | + * automatically. |
| 1072 | + */ |
| 1073 | + if (skip_prefix(url, "http::", out) || |
| 1074 | + skip_prefix(url, "https::", out) || |
| 1075 | + skip_prefix(url, "ftp::", out) || |
| 1076 | + skip_prefix(url, "ftps::", out)) |
| 1077 | + return 1; |
| 1078 | + if (starts_with(url, "http://") || |
| 1079 | + starts_with(url, "https://") || |
| 1080 | + starts_with(url, "ftp://") || |
| 1081 | + starts_with(url, "ftps://")) { |
| 1082 | + *out = url; |
| 1083 | + return 1; |
| 1084 | + } |
| 1085 | + return 0; |
| 1086 | +} |
| 1087 | + |
992 | 1088 | static int check_submodule_url(const char *url)
|
993 | 1089 | {
|
994 |
| - struct credential c = CREDENTIAL_INIT; |
995 |
| - int ret; |
| 1090 | + const char *curl_url; |
996 | 1091 |
|
997 | 1092 | if (looks_like_command_line_option(url))
|
998 | 1093 | return -1;
|
999 | 1094 |
|
1000 |
| - ret = credential_from_url_gently(&c, url, 1); |
1001 |
| - credential_clear(&c); |
1002 |
| - return ret; |
| 1095 | + if (submodule_url_is_relative(url)) { |
| 1096 | + char *decoded; |
| 1097 | + const char *next; |
| 1098 | + int has_nl; |
| 1099 | + |
| 1100 | + /* |
| 1101 | + * This could be appended to an http URL and url-decoded; |
| 1102 | + * check for malicious characters. |
| 1103 | + */ |
| 1104 | + decoded = url_decode(url); |
| 1105 | + has_nl = !!strchr(decoded, '\n'); |
| 1106 | + |
| 1107 | + free(decoded); |
| 1108 | + if (has_nl) |
| 1109 | + return -1; |
| 1110 | + |
| 1111 | + /* |
| 1112 | + * URLs which escape their root via "../" can overwrite |
| 1113 | + * the host field and previous components, resolving to |
| 1114 | + * URLs like https::example.com/submodule.git and |
| 1115 | + * https:///example.com/submodule.git that were |
| 1116 | + * susceptible to CVE-2020-11008. |
| 1117 | + */ |
| 1118 | + if (count_leading_dotdots(url, &next) > 0 && |
| 1119 | + (*next == ':' || *next == '/')) |
| 1120 | + return -1; |
| 1121 | + } |
| 1122 | + |
| 1123 | + else if (url_to_curl_url(url, &curl_url)) { |
| 1124 | + struct credential c = CREDENTIAL_INIT; |
| 1125 | + int ret = 0; |
| 1126 | + if (credential_from_url_gently(&c, curl_url, 1) || |
| 1127 | + !*c.host) |
| 1128 | + ret = -1; |
| 1129 | + credential_clear(&c); |
| 1130 | + return ret; |
| 1131 | + } |
| 1132 | + |
| 1133 | + return 0; |
1003 | 1134 | }
|
1004 | 1135 |
|
1005 | 1136 | struct fsck_gitmodules_data {
|
|
0 commit comments