Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1 | From a5adaced2e13c135d5d9cc65be9eb95aa3bacedf Mon Sep 17 00:00:00 2001 |
| 2 | From: Jeff King <peff@peff.net> |
| 3 | Date: Wed, 16 Sep 2015 13:12:52 -0400 |
| 4 | Subject: [PATCH] transport: add a protocol-whitelist environment variable |
| 5 | |
| 6 | If we are cloning an untrusted remote repository into a |
| 7 | sandbox, we may also want to fetch remote submodules in |
| 8 | order to get the complete view as intended by the other |
| 9 | side. However, that opens us up to attacks where a malicious |
| 10 | user gets us to clone something they would not otherwise |
| 11 | have access to (this is not necessarily a problem by itself, |
| 12 | but we may then act on the cloned contents in a way that |
| 13 | exposes them to the attacker). |
| 14 | |
| 15 | Ideally such a setup would sandbox git entirely away from |
| 16 | high-value items, but this is not always practical or easy |
| 17 | to set up (e.g., OS network controls may block multiple |
| 18 | protocols, and we would want to enable some but not others). |
| 19 | |
| 20 | We can help this case by providing a way to restrict |
| 21 | particular protocols. We use a whitelist in the environment. |
| 22 | This is more annoying to set up than a blacklist, but |
| 23 | defaults to safety if the set of protocols git supports |
| 24 | grows). If no whitelist is specified, we continue to default |
| 25 | to allowing all protocols (this is an "unsafe" default, but |
| 26 | since the minority of users will want this sandboxing |
| 27 | effect, it is the only sensible one). |
| 28 | |
| 29 | A note on the tests: ideally these would all be in a single |
| 30 | test file, but the git-daemon and httpd test infrastructure |
| 31 | is an all-or-nothing proposition rather than a test-by-test |
| 32 | prerequisite. By putting them all together, we would be |
| 33 | unable to test the file-local code on machines without |
| 34 | apache. |
| 35 | |
| 36 | Signed-off-by: Jeff King <peff@peff.net> |
| 37 | Signed-off-by: Junio C Hamano <gitster@pobox.com> |
| 38 | |
| 39 | Upstream-Status: Backport |
| 40 | |
| 41 | http://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.5.0-1ubuntu0.1.debian.tar.xz |
| 42 | |
| 43 | CVE: CVE-2015-7545 #1 |
| 44 | Singed-off-by: Armin Kuster <akuster@mvista.com> |
| 45 | |
| 46 | --- |
| 47 | Documentation/git.txt | 32 ++++++++++++++ |
| 48 | connect.c | 5 +++ |
| 49 | t/lib-proto-disable.sh | 96 ++++++++++++++++++++++++++++++++++++++++++ |
| 50 | t/t5810-proto-disable-local.sh | 14 ++++++ |
| 51 | t/t5811-proto-disable-git.sh | 20 +++++++++ |
| 52 | t/t5812-proto-disable-http.sh | 20 +++++++++ |
| 53 | t/t5813-proto-disable-ssh.sh | 20 +++++++++ |
| 54 | t/t5814-proto-disable-ext.sh | 18 ++++++++ |
| 55 | transport-helper.c | 2 + |
| 56 | transport.c | 21 ++++++++- |
| 57 | transport.h | 7 +++ |
| 58 | 11 files changed, 254 insertions(+), 1 deletion(-) |
| 59 | create mode 100644 t/lib-proto-disable.sh |
| 60 | create mode 100755 t/t5810-proto-disable-local.sh |
| 61 | create mode 100755 t/t5811-proto-disable-git.sh |
| 62 | create mode 100755 t/t5812-proto-disable-http.sh |
| 63 | create mode 100755 t/t5813-proto-disable-ssh.sh |
| 64 | create mode 100755 t/t5814-proto-disable-ext.sh |
| 65 | |
| 66 | Index: git-2.5.0/Documentation/git.txt |
| 67 | =================================================================== |
| 68 | --- git-2.5.0.orig/Documentation/git.txt 2015-12-11 12:46:48.975637719 -0500 |
| 69 | +++ git-2.5.0/Documentation/git.txt 2015-12-11 12:46:48.967637661 -0500 |
| 70 | @@ -1069,6 +1069,38 @@ |
| 71 | an operation has touched every ref (e.g., because you are |
| 72 | cloning a repository to make a backup). |
| 73 | |
| 74 | +`GIT_ALLOW_PROTOCOL`:: |
| 75 | + If set, provide a colon-separated list of protocols which are |
| 76 | + allowed to be used with fetch/push/clone. This is useful to |
| 77 | + restrict recursive submodule initialization from an untrusted |
| 78 | + repository. Any protocol not mentioned will be disallowed (i.e., |
| 79 | + this is a whitelist, not a blacklist). If the variable is not |
| 80 | + set at all, all protocols are enabled. The protocol names |
| 81 | + currently used by git are: |
| 82 | + |
| 83 | + - `file`: any local file-based path (including `file://` URLs, |
| 84 | + or local paths) |
| 85 | + |
| 86 | + - `git`: the anonymous git protocol over a direct TCP |
| 87 | + connection (or proxy, if configured) |
| 88 | + |
| 89 | + - `ssh`: git over ssh (including `host:path` syntax, |
| 90 | + `git+ssh://`, etc). |
| 91 | + |
| 92 | + - `rsync`: git over rsync |
| 93 | + |
| 94 | + - `http`: git over http, both "smart http" and "dumb http". |
| 95 | + Note that this does _not_ include `https`; if you want both, |
| 96 | + you should specify both as `http:https`. |
| 97 | + |
| 98 | + - any external helpers are named by their protocol (e.g., use |
| 99 | + `hg` to allow the `git-remote-hg` helper) |
| 100 | ++ |
| 101 | +Note that this controls only git's internal protocol selection. |
| 102 | +If libcurl is used (e.g., by the `http` transport), it may |
| 103 | +redirect to other protocols. There is not currently any way to |
| 104 | +restrict this. |
| 105 | + |
| 106 | |
| 107 | Discussion[[Discussion]] |
| 108 | ------------------------ |
| 109 | Index: git-2.5.0/connect.c |
| 110 | =================================================================== |
| 111 | --- git-2.5.0.orig/connect.c 2015-12-11 12:46:48.975637719 -0500 |
| 112 | +++ git-2.5.0/connect.c 2015-12-11 12:46:48.967637661 -0500 |
| 113 | @@ -9,6 +9,7 @@ |
| 114 | #include "url.h" |
| 115 | #include "string-list.h" |
| 116 | #include "sha1-array.h" |
| 117 | +#include "transport.h" |
| 118 | |
| 119 | static char *server_capabilities; |
| 120 | static const char *parse_feature_value(const char *, const char *, int *); |
| 121 | @@ -694,6 +695,8 @@ |
| 122 | else |
| 123 | target_host = xstrdup(hostandport); |
| 124 | |
| 125 | + transport_check_allowed("git"); |
| 126 | + |
| 127 | /* These underlying connection commands die() if they |
| 128 | * cannot connect. |
| 129 | */ |
| 130 | @@ -727,6 +730,7 @@ |
| 131 | int putty, tortoiseplink = 0; |
| 132 | char *ssh_host = hostandport; |
| 133 | const char *port = NULL; |
| 134 | + transport_check_allowed("ssh"); |
| 135 | get_host_and_port(&ssh_host, &port); |
| 136 | |
| 137 | if (!port) |
| 138 | @@ -781,6 +785,7 @@ |
| 139 | /* remove repo-local variables from the environment */ |
| 140 | conn->env = local_repo_env; |
| 141 | conn->use_shell = 1; |
| 142 | + transport_check_allowed("file"); |
| 143 | } |
| 144 | argv_array_push(&conn->args, cmd.buf); |
| 145 | |
| 146 | Index: git-2.5.0/t/lib-proto-disable.sh |
| 147 | =================================================================== |
| 148 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 |
| 149 | +++ git-2.5.0/t/lib-proto-disable.sh 2015-12-11 12:46:48.967637661 -0500 |
| 150 | @@ -0,0 +1,96 @@ |
| 151 | +# Test routines for checking protocol disabling. |
| 152 | + |
| 153 | +# test cloning a particular protocol |
| 154 | +# $1 - description of the protocol |
| 155 | +# $2 - machine-readable name of the protocol |
| 156 | +# $3 - the URL to try cloning |
| 157 | +test_proto () { |
| 158 | + desc=$1 |
| 159 | + proto=$2 |
| 160 | + url=$3 |
| 161 | + |
| 162 | + test_expect_success "clone $1 (enabled)" ' |
| 163 | + rm -rf tmp.git && |
| 164 | + ( |
| 165 | + GIT_ALLOW_PROTOCOL=$proto && |
| 166 | + export GIT_ALLOW_PROTOCOL && |
| 167 | + git clone --bare "$url" tmp.git |
| 168 | + ) |
| 169 | + ' |
| 170 | + |
| 171 | + test_expect_success "fetch $1 (enabled)" ' |
| 172 | + ( |
| 173 | + cd tmp.git && |
| 174 | + GIT_ALLOW_PROTOCOL=$proto && |
| 175 | + export GIT_ALLOW_PROTOCOL && |
| 176 | + git fetch |
| 177 | + ) |
| 178 | + ' |
| 179 | + |
| 180 | + test_expect_success "push $1 (enabled)" ' |
| 181 | + ( |
| 182 | + cd tmp.git && |
| 183 | + GIT_ALLOW_PROTOCOL=$proto && |
| 184 | + export GIT_ALLOW_PROTOCOL && |
| 185 | + git push origin HEAD:pushed |
| 186 | + ) |
| 187 | + ' |
| 188 | + |
| 189 | + test_expect_success "push $1 (disabled)" ' |
| 190 | + ( |
| 191 | + cd tmp.git && |
| 192 | + GIT_ALLOW_PROTOCOL=none && |
| 193 | + export GIT_ALLOW_PROTOCOL && |
| 194 | + test_must_fail git push origin HEAD:pushed |
| 195 | + ) |
| 196 | + ' |
| 197 | + |
| 198 | + test_expect_success "fetch $1 (disabled)" ' |
| 199 | + ( |
| 200 | + cd tmp.git && |
| 201 | + GIT_ALLOW_PROTOCOL=none && |
| 202 | + export GIT_ALLOW_PROTOCOL && |
| 203 | + test_must_fail git fetch |
| 204 | + ) |
| 205 | + ' |
| 206 | + |
| 207 | + test_expect_success "clone $1 (disabled)" ' |
| 208 | + rm -rf tmp.git && |
| 209 | + ( |
| 210 | + GIT_ALLOW_PROTOCOL=none && |
| 211 | + export GIT_ALLOW_PROTOCOL && |
| 212 | + test_must_fail git clone --bare "$url" tmp.git |
| 213 | + ) |
| 214 | + ' |
| 215 | +} |
| 216 | + |
| 217 | +# set up an ssh wrapper that will access $host/$repo in the |
| 218 | +# trash directory, and enable it for subsequent tests. |
| 219 | +setup_ssh_wrapper () { |
| 220 | + test_expect_success 'setup ssh wrapper' ' |
| 221 | + write_script ssh-wrapper <<-\EOF && |
| 222 | + echo >&2 "ssh: $*" |
| 223 | + host=$1; shift |
| 224 | + cd "$TRASH_DIRECTORY/$host" && |
| 225 | + eval "$*" |
| 226 | + EOF |
| 227 | + GIT_SSH="$PWD/ssh-wrapper" && |
| 228 | + export GIT_SSH && |
| 229 | + export TRASH_DIRECTORY |
| 230 | + ' |
| 231 | +} |
| 232 | + |
| 233 | +# set up a wrapper that can be used with remote-ext to |
| 234 | +# access repositories in the "remote" directory of trash-dir, |
| 235 | +# like "ext::fake-remote %S repo.git" |
| 236 | +setup_ext_wrapper () { |
| 237 | + test_expect_success 'setup ext wrapper' ' |
| 238 | + write_script fake-remote <<-\EOF && |
| 239 | + echo >&2 "fake-remote: $*" |
| 240 | + cd "$TRASH_DIRECTORY/remote" && |
| 241 | + eval "$*" |
| 242 | + EOF |
| 243 | + PATH=$TRASH_DIRECTORY:$PATH && |
| 244 | + export TRASH_DIRECTORY |
| 245 | + ' |
| 246 | +} |
| 247 | Index: git-2.5.0/t/t5810-proto-disable-local.sh |
| 248 | =================================================================== |
| 249 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 |
| 250 | +++ git-2.5.0/t/t5810-proto-disable-local.sh 2015-12-11 12:46:48.967637661 -0500 |
| 251 | @@ -0,0 +1,14 @@ |
| 252 | +#!/bin/sh |
| 253 | + |
| 254 | +test_description='test disabling of local paths in clone/fetch' |
| 255 | +. ./test-lib.sh |
| 256 | +. "$TEST_DIRECTORY/lib-proto-disable.sh" |
| 257 | + |
| 258 | +test_expect_success 'setup repository to clone' ' |
| 259 | + test_commit one |
| 260 | +' |
| 261 | + |
| 262 | +test_proto "file://" file "file://$PWD" |
| 263 | +test_proto "path" file . |
| 264 | + |
| 265 | +test_done |
| 266 | Index: git-2.5.0/t/t5811-proto-disable-git.sh |
| 267 | =================================================================== |
| 268 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 |
| 269 | +++ git-2.5.0/t/t5811-proto-disable-git.sh 2015-12-11 12:46:48.967637661 -0500 |
| 270 | @@ -0,0 +1,20 @@ |
| 271 | +#!/bin/sh |
| 272 | + |
| 273 | +test_description='test disabling of git-over-tcp in clone/fetch' |
| 274 | +. ./test-lib.sh |
| 275 | +. "$TEST_DIRECTORY/lib-proto-disable.sh" |
| 276 | +. "$TEST_DIRECTORY/lib-git-daemon.sh" |
| 277 | +start_git_daemon |
| 278 | + |
| 279 | +test_expect_success 'create git-accessible repo' ' |
| 280 | + bare="$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" && |
| 281 | + test_commit one && |
| 282 | + git --bare init "$bare" && |
| 283 | + git push "$bare" HEAD && |
| 284 | + >"$bare/git-daemon-export-ok" && |
| 285 | + git -C "$bare" config daemon.receivepack true |
| 286 | +' |
| 287 | + |
| 288 | +test_proto "git://" git "$GIT_DAEMON_URL/repo.git" |
| 289 | + |
| 290 | +test_done |
| 291 | Index: git-2.5.0/t/t5812-proto-disable-http.sh |
| 292 | =================================================================== |
| 293 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 |
| 294 | +++ git-2.5.0/t/t5812-proto-disable-http.sh 2015-12-11 12:46:48.967637661 -0500 |
| 295 | @@ -0,0 +1,20 @@ |
| 296 | +#!/bin/sh |
| 297 | + |
| 298 | +test_description='test disabling of git-over-http in clone/fetch' |
| 299 | +. ./test-lib.sh |
| 300 | +. "$TEST_DIRECTORY/lib-proto-disable.sh" |
| 301 | +. "$TEST_DIRECTORY/lib-httpd.sh" |
| 302 | +start_httpd |
| 303 | + |
| 304 | +test_expect_success 'create git-accessible repo' ' |
| 305 | + bare="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && |
| 306 | + test_commit one && |
| 307 | + git --bare init "$bare" && |
| 308 | + git push "$bare" HEAD && |
| 309 | + git -C "$bare" config http.receivepack true |
| 310 | +' |
| 311 | + |
| 312 | +test_proto "smart http" http "$HTTPD_URL/smart/repo.git" |
| 313 | + |
| 314 | +stop_httpd |
| 315 | +test_done |
| 316 | Index: git-2.5.0/t/t5813-proto-disable-ssh.sh |
| 317 | =================================================================== |
| 318 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 |
| 319 | +++ git-2.5.0/t/t5813-proto-disable-ssh.sh 2015-12-11 12:46:48.967637661 -0500 |
| 320 | @@ -0,0 +1,20 @@ |
| 321 | +#!/bin/sh |
| 322 | + |
| 323 | +test_description='test disabling of git-over-ssh in clone/fetch' |
| 324 | +. ./test-lib.sh |
| 325 | +. "$TEST_DIRECTORY/lib-proto-disable.sh" |
| 326 | + |
| 327 | +setup_ssh_wrapper |
| 328 | + |
| 329 | +test_expect_success 'setup repository to clone' ' |
| 330 | + test_commit one && |
| 331 | + mkdir remote && |
| 332 | + git init --bare remote/repo.git && |
| 333 | + git push remote/repo.git HEAD |
| 334 | +' |
| 335 | + |
| 336 | +test_proto "host:path" ssh "remote:repo.git" |
| 337 | +test_proto "ssh://" ssh "ssh://remote/$PWD/remote/repo.git" |
| 338 | +test_proto "git+ssh://" ssh "git+ssh://remote/$PWD/remote/repo.git" |
| 339 | + |
| 340 | +test_done |
| 341 | Index: git-2.5.0/t/t5814-proto-disable-ext.sh |
| 342 | =================================================================== |
| 343 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 |
| 344 | +++ git-2.5.0/t/t5814-proto-disable-ext.sh 2015-12-11 12:46:48.967637661 -0500 |
| 345 | @@ -0,0 +1,18 @@ |
| 346 | +#!/bin/sh |
| 347 | + |
| 348 | +test_description='test disabling of remote-helper paths in clone/fetch' |
| 349 | +. ./test-lib.sh |
| 350 | +. "$TEST_DIRECTORY/lib-proto-disable.sh" |
| 351 | + |
| 352 | +setup_ext_wrapper |
| 353 | + |
| 354 | +test_expect_success 'setup repository to clone' ' |
| 355 | + test_commit one && |
| 356 | + mkdir remote && |
| 357 | + git init --bare remote/repo.git && |
| 358 | + git push remote/repo.git HEAD |
| 359 | +' |
| 360 | + |
| 361 | +test_proto "remote-helper" ext "ext::fake-remote %S repo.git" |
| 362 | + |
| 363 | +test_done |
| 364 | Index: git-2.5.0/transport-helper.c |
| 365 | =================================================================== |
| 366 | --- git-2.5.0.orig/transport-helper.c 2015-12-11 12:46:48.975637719 -0500 |
| 367 | +++ git-2.5.0/transport-helper.c 2015-12-11 12:46:48.967637661 -0500 |
| 368 | @@ -1039,6 +1039,8 @@ |
| 369 | struct helper_data *data = xcalloc(1, sizeof(*data)); |
| 370 | data->name = name; |
| 371 | |
| 372 | + transport_check_allowed(name); |
| 373 | + |
| 374 | if (getenv("GIT_TRANSPORT_HELPER_DEBUG")) |
| 375 | debug = 1; |
| 376 | |
| 377 | Index: git-2.5.0/transport.c |
| 378 | =================================================================== |
| 379 | --- git-2.5.0.orig/transport.c 2015-12-11 12:46:48.975637719 -0500 |
| 380 | +++ git-2.5.0/transport.c 2015-12-11 12:46:48.967637661 -0500 |
| 381 | @@ -912,6 +912,20 @@ |
| 382 | return strchr(url, ':') - url; |
| 383 | } |
| 384 | |
| 385 | +void transport_check_allowed(const char *type) |
| 386 | +{ |
| 387 | + struct string_list allowed = STRING_LIST_INIT_DUP; |
| 388 | + const char *v = getenv("GIT_ALLOW_PROTOCOL"); |
| 389 | + |
| 390 | + if (!v) |
| 391 | + return; |
| 392 | + |
| 393 | + string_list_split(&allowed, v, ':', -1); |
| 394 | + if (!unsorted_string_list_has_string(&allowed, type)) |
| 395 | + die("transport '%s' not allowed", type); |
| 396 | + string_list_clear(&allowed, 0); |
| 397 | +} |
| 398 | + |
| 399 | struct transport *transport_get(struct remote *remote, const char *url) |
| 400 | { |
| 401 | const char *helper; |
| 402 | @@ -943,12 +957,14 @@ |
| 403 | if (helper) { |
| 404 | transport_helper_init(ret, helper); |
| 405 | } else if (starts_with(url, "rsync:")) { |
| 406 | + transport_check_allowed("rsync"); |
| 407 | ret->get_refs_list = get_refs_via_rsync; |
| 408 | ret->fetch = fetch_objs_via_rsync; |
| 409 | ret->push = rsync_transport_push; |
| 410 | ret->smart_options = NULL; |
| 411 | } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) { |
| 412 | struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); |
| 413 | + transport_check_allowed("file"); |
| 414 | ret->data = data; |
| 415 | ret->get_refs_list = get_refs_from_bundle; |
| 416 | ret->fetch = fetch_refs_from_bundle; |
| 417 | @@ -960,7 +976,10 @@ |
| 418 | || starts_with(url, "ssh://") |
| 419 | || starts_with(url, "git+ssh://") |
| 420 | || starts_with(url, "ssh+git://")) { |
| 421 | - /* These are builtin smart transports. */ |
| 422 | + /* |
| 423 | + * These are builtin smart transports; "allowed" transports |
| 424 | + * will be checked individually in git_connect. |
| 425 | + */ |
| 426 | struct git_transport_data *data = xcalloc(1, sizeof(*data)); |
| 427 | ret->data = data; |
| 428 | ret->set_option = NULL; |
| 429 | Index: git-2.5.0/transport.h |
| 430 | =================================================================== |
| 431 | --- git-2.5.0.orig/transport.h 2015-12-11 12:46:48.975637719 -0500 |
| 432 | +++ git-2.5.0/transport.h 2015-12-11 12:46:48.971637690 -0500 |
| 433 | @@ -133,6 +133,13 @@ |
| 434 | /* Returns a transport suitable for the url */ |
| 435 | struct transport *transport_get(struct remote *, const char *); |
| 436 | |
| 437 | +/* |
| 438 | + * Check whether a transport is allowed by the environment, |
| 439 | + * and die otherwise. type should generally be the URL scheme, |
| 440 | + * as described in Documentation/git.txt |
| 441 | + */ |
| 442 | +void transport_check_allowed(const char *type); |
| 443 | + |
| 444 | /* Transport options which apply to git:// and scp-style URLs */ |
| 445 | |
| 446 | /* The program to use on the remote side to send a pack */ |