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