Git fork

Sync with 2.43.6

* maint-2.43:
Git 2.43.6
Git 2.42.4
Git 2.41.3
Git 2.40.4
credential: disallow Carriage Returns in the protocol by default
credential: sanitize the user prompt
credential_format(): also encode <host>[:<port>]
t7300: work around platform-specific behaviour with long paths on MinGW
compat/regex: fix argument order to calloc(3)
mingw: drop bogus (and unneeded) declaration of `_pgmptr`
ci: remove 'Upload failed tests' directories' step from linux32 jobs

+150 -53
-6
.github/workflows/main.yml
··· 356 356 with: 357 357 name: failed-tests-${{matrix.vector.jobname}} 358 358 path: ${{env.FAILED_TEST_ARTIFACTS}} 359 - - name: Upload failed tests' directories 360 - if: failure() && env.FAILED_TEST_ARTIFACTS != '' && matrix.vector.jobname == 'linux32' 361 - uses: actions/upload-artifact@v1 # cannot be upgraded because Node.js Actions aren't supported in this container 362 - with: 363 - name: failed-tests-${{matrix.vector.jobname}} 364 - path: ${{env.FAILED_TEST_ARTIFACTS}} 365 359 static-analysis: 366 360 needs: ci-config 367 361 if: needs.ci-config.outputs.enabled == 'yes'
+5
Documentation/RelNotes/2.40.4.txt
··· 1 + Git v2.40.4 Release Notes 2 + ========================= 3 + 4 + This release lets Git refuse to accept URLs that contain control 5 + sequences. This addresses CVE-2024-50349 and CVE-2024-52006.
+6
Documentation/RelNotes/2.41.3.txt
··· 1 + Git v2.41.3 Release Notes 2 + ========================= 3 + 4 + This release merges up the fix that appears in v2.40.4 to address 5 + the security issues CVE-2024-50349 and CVE-2024-52006; see the 6 + release notes for that version for details.
+6
Documentation/RelNotes/2.42.4.txt
··· 1 + Git v2.42.4 Release Notes 2 + ========================= 3 + 4 + This release merges up the fix that appears in v2.40.4 and v2.41.3 5 + to address the security issues CVE-2024-50349 and CVE-2024-52006; 6 + see the release notes for these versions for details.
+7
Documentation/RelNotes/2.43.6.txt
··· 1 + Git v2.43.6 Release Notes 2 + ========================= 3 + 4 + This release merges up the fix that appears in v2.40.4, v2.41.3 5 + and v2.42.4 to address the security issues CVE-2024-50349 and 6 + CVE-2024-52006; see the release notes for these versions for 7 + details.
+11
Documentation/config/credential.txt
··· 14 14 or https URL to be important. Defaults to false. See 15 15 linkgit:gitcredentials[7] for more information. 16 16 17 + credential.sanitizePrompt:: 18 + By default, user names and hosts that are shown as part of the 19 + password prompt are not allowed to contain control characters (they 20 + will be URL-encoded by default). Configure this setting to `false` to 21 + override that behavior. 22 + 23 + credential.protectProtocol:: 24 + By default, Carriage Return characters are not allowed in the protocol 25 + that is used when Git talks to a credential helper. This setting allows 26 + users to override this default. 27 + 17 28 credential.username:: 18 29 If no username is set for a network authentication, use this username 19 30 by default. See credential.<context>.* below, and
-1
compat/mingw.c
··· 26 26 void open_in_gdb(void) 27 27 { 28 28 static struct child_process cp = CHILD_PROCESS_INIT; 29 - extern char *_pgmptr; 30 29 31 30 strvec_pushl(&cp.args, "mintty", "gdb", NULL); 32 31 strvec_pushf(&cp.args, "--pid=%d", getpid());
+6 -6
compat/regex/regcomp.c
··· 868 868 if (table_size > pat_len) 869 869 break; 870 870 871 - dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size); 871 + dfa->state_table = calloc (table_size, sizeof (struct re_state_table_entry)); 872 872 dfa->state_hash_mask = table_size - 1; 873 873 874 874 dfa->mb_cur_max = MB_CUR_MAX; ··· 936 936 { 937 937 int i, j, ch; 938 938 939 - dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); 939 + dfa->sb_char = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t)); 940 940 if (BE (dfa->sb_char == NULL, 0)) 941 941 return REG_ESPACE; 942 942 ··· 3079 3079 _NL_COLLATE_SYMB_EXTRAMB); 3080 3080 } 3081 3081 #endif 3082 - sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); 3082 + sbcset = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t)); 3083 3083 #ifdef RE_ENABLE_I18N 3084 - mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); 3084 + mbcset = (re_charset_t *) calloc (1, sizeof (re_charset_t)); 3085 3085 #endif /* RE_ENABLE_I18N */ 3086 3086 #ifdef RE_ENABLE_I18N 3087 3087 if (BE (sbcset == NULL || mbcset == NULL, 0)) ··· 3626 3626 re_token_t br_token; 3627 3627 bin_tree_t *tree; 3628 3628 3629 - sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); 3629 + sbcset = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t)); 3630 3630 #ifdef RE_ENABLE_I18N 3631 - mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); 3631 + mbcset = (re_charset_t *) calloc (1, sizeof (re_charset_t)); 3632 3632 #endif /* RE_ENABLE_I18N */ 3633 3633 3634 3634 #ifdef RE_ENABLE_I18N
+2 -2
compat/regex/regex_internal.c
··· 1628 1628 reg_errcode_t err; 1629 1629 re_dfastate_t *newstate; 1630 1630 1631 - newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); 1631 + newstate = (re_dfastate_t *) calloc (1, sizeof (re_dfastate_t)); 1632 1632 if (BE (newstate == NULL, 0)) 1633 1633 return NULL; 1634 1634 err = re_node_set_init_copy (&newstate->nodes, nodes); ··· 1678 1678 reg_errcode_t err; 1679 1679 re_dfastate_t *newstate; 1680 1680 1681 - newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); 1681 + newstate = (re_dfastate_t *) calloc (1, sizeof (re_dfastate_t)); 1682 1682 if (BE (newstate == NULL, 0)) 1683 1683 return NULL; 1684 1684 err = re_node_set_init_copy (&newstate->nodes, nodes);
+5 -5
compat/regex/regexec.c
··· 2796 2796 continue; /* No. */ 2797 2797 if (sub_top->path == NULL) 2798 2798 { 2799 - sub_top->path = calloc (sizeof (state_array_t), 2800 - sl_str - sub_top->str_idx + 1); 2799 + sub_top->path = calloc (sl_str - sub_top->str_idx + 1, 2800 + sizeof (state_array_t)); 2801 2801 if (sub_top->path == NULL) 2802 2802 return REG_ESPACE; 2803 2803 } ··· 3361 3361 if (ndests == 0) 3362 3362 { 3363 3363 state->trtable = (re_dfastate_t **) 3364 - calloc (sizeof (re_dfastate_t *), SBC_MAX); 3364 + calloc (SBC_MAX, sizeof (re_dfastate_t *)); 3365 3365 return 1; 3366 3366 } 3367 3367 return 0; ··· 3457 3457 discern by looking at the character code: allocate a 3458 3458 256-entry transition table. */ 3459 3459 trtable = state->trtable = 3460 - (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); 3460 + (re_dfastate_t **) calloc (SBC_MAX, sizeof (re_dfastate_t *)); 3461 3461 if (BE (trtable == NULL, 0)) 3462 3462 goto out_free; 3463 3463 ··· 3488 3488 transition tables, one starting at trtable[0] and one 3489 3489 starting at trtable[SBC_MAX]. */ 3490 3490 trtable = state->word_trtable = 3491 - (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX); 3491 + (re_dfastate_t **) calloc (2 * SBC_MAX, sizeof (re_dfastate_t *)); 3492 3492 if (BE (trtable == NULL, 0)) 3493 3493 goto out_free; 3494 3494
+25 -12
credential.c
··· 10 10 #include "sigchain.h" 11 11 #include "strbuf.h" 12 12 #include "urlmatch.h" 13 - #include "git-compat-util.h" 13 + #include "environment.h" 14 14 15 15 void credential_init(struct credential *c) 16 16 { ··· 74 74 } 75 75 else if (!strcmp(key, "usehttppath")) 76 76 c->use_http_path = git_config_bool(var, value); 77 + else if (!strcmp(key, "sanitizeprompt")) 78 + c->sanitize_prompt = git_config_bool(var, value); 79 + else if (!strcmp(key, "protectprotocol")) 80 + c->protect_protocol = git_config_bool(var, value); 77 81 78 82 return 0; 79 83 } ··· 171 175 strbuf_addch(out, '@'); 172 176 } 173 177 if (c->host) 174 - strbuf_addstr(out, c->host); 178 + strbuf_add_percentencode(out, c->host, 179 + STRBUF_ENCODE_HOST_AND_PORT); 175 180 if (c->path) { 176 181 strbuf_addch(out, '/'); 177 182 strbuf_add_percentencode(out, c->path, 0); ··· 185 190 struct strbuf prompt = STRBUF_INIT; 186 191 char *r; 187 192 188 - credential_describe(c, &desc); 193 + if (c->sanitize_prompt) 194 + credential_format(c, &desc); 195 + else 196 + credential_describe(c, &desc); 189 197 if (desc.len) 190 198 strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf); 191 199 else ··· 268 276 return 0; 269 277 } 270 278 271 - static void credential_write_item(FILE *fp, const char *key, const char *value, 279 + static void credential_write_item(const struct credential *c, 280 + FILE *fp, const char *key, const char *value, 272 281 int required) 273 282 { 274 283 if (!value && required) ··· 277 286 return; 278 287 if (strchr(value, '\n')) 279 288 die("credential value for %s contains newline", key); 289 + if (c->protect_protocol && strchr(value, '\r')) 290 + die("credential value for %s contains carriage return\n" 291 + "If this is intended, set `credential.protectProtocol=false`", 292 + key); 280 293 fprintf(fp, "%s=%s\n", key, value); 281 294 } 282 295 283 296 void credential_write(const struct credential *c, FILE *fp) 284 297 { 285 - credential_write_item(fp, "protocol", c->protocol, 1); 286 - credential_write_item(fp, "host", c->host, 1); 287 - credential_write_item(fp, "path", c->path, 0); 288 - credential_write_item(fp, "username", c->username, 0); 289 - credential_write_item(fp, "password", c->password, 0); 290 - credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0); 298 + credential_write_item(c, fp, "protocol", c->protocol, 1); 299 + credential_write_item(c, fp, "host", c->host, 1); 300 + credential_write_item(c, fp, "path", c->path, 0); 301 + credential_write_item(c, fp, "username", c->username, 0); 302 + credential_write_item(c, fp, "password", c->password, 0); 303 + credential_write_item(c, fp, "oauth_refresh_token", c->oauth_refresh_token, 0); 291 304 if (c->password_expiry_utc != TIME_MAX) { 292 305 char *s = xstrfmt("%"PRItime, c->password_expiry_utc); 293 - credential_write_item(fp, "password_expiry_utc", s, 0); 306 + credential_write_item(c, fp, "password_expiry_utc", s, 0); 294 307 free(s); 295 308 } 296 309 for (size_t i = 0; i < c->wwwauth_headers.nr; i++) 297 - credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); 310 + credential_write_item(c, fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); 298 311 } 299 312 300 313 static int run_credential_helper(struct credential *c,
+5 -1
credential.h
··· 134 134 configured:1, 135 135 quit:1, 136 136 use_http_path:1, 137 - username_from_proto:1; 137 + username_from_proto:1, 138 + sanitize_prompt:1, 139 + protect_protocol:1; 138 140 139 141 char *username; 140 142 char *password; ··· 149 151 .helpers = STRING_LIST_INIT_DUP, \ 150 152 .password_expiry_utc = TIME_MAX, \ 151 153 .wwwauth_headers = STRVEC_INIT, \ 154 + .sanitize_prompt = 1, \ 155 + .protect_protocol = 1, \ 152 156 } 153 157 154 158 /* Initialize a credential structure, setting all fields to empty. */
+3 -1
strbuf.c
··· 463 463 unsigned char ch = src[i]; 464 464 if (ch <= 0x1F || ch >= 0x7F || 465 465 (ch == '/' && (flags & STRBUF_ENCODE_SLASH)) || 466 - strchr(URL_UNSAFE_CHARS, ch)) 466 + ((flags & STRBUF_ENCODE_HOST_AND_PORT) ? 467 + !isalnum(ch) && !strchr("-.:[]", ch) : 468 + !!strchr(URL_UNSAFE_CHARS, ch))) 467 469 strbuf_addf(dst, "%%%02X", (unsigned char)ch); 468 470 else 469 471 strbuf_addch(dst, ch);
+1
strbuf.h
··· 346 346 void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); 347 347 348 348 #define STRBUF_ENCODE_SLASH 1 349 + #define STRBUF_ENCODE_HOST_AND_PORT 2 349 350 350 351 /** 351 352 * Append the contents of a string to a strbuf, percent-encoding any characters
+49
t/t0300-credentials.sh
··· 45 45 test -z "$pexpiry" || echo password_expiry_utc=$pexpiry 46 46 EOF 47 47 48 + write_script git-credential-cntrl-in-username <<-\EOF && 49 + printf "username=\\007latrix Lestrange\\n" 50 + EOF 51 + 48 52 PATH="$PWD:$PATH" 49 53 ' 50 54 ··· 532 536 EOF 533 537 ' 534 538 539 + test_expect_success 'match percent-encoded values in hostname' ' 540 + test_config "credential.https://a%20b%20c/.helper" "$HELPER" && 541 + check fill <<-\EOF 542 + url=https://a b c/ 543 + -- 544 + protocol=https 545 + host=a b c 546 + username=foo 547 + password=bar 548 + -- 549 + EOF 550 + ' 551 + 535 552 test_expect_success 'fetch with multiple path components' ' 536 553 test_unconfig credential.helper && 537 554 test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" && ··· 721 738 test_cmp expect stderr 722 739 ' 723 740 741 + test_expect_success 'url parser rejects embedded carriage returns' ' 742 + test_config credential.helper "!true" && 743 + test_must_fail git credential fill 2>stderr <<-\EOF && 744 + url=https://example%0d.com/ 745 + EOF 746 + cat >expect <<-\EOF && 747 + fatal: credential value for host contains carriage return 748 + If this is intended, set `credential.protectProtocol=false` 749 + EOF 750 + test_cmp expect stderr && 751 + GIT_ASKPASS=true \ 752 + git -c credential.protectProtocol=false credential fill <<-\EOF 753 + url=https://example%0d.com/ 754 + EOF 755 + ' 756 + 724 757 test_expect_success 'host-less URLs are parsed as empty host' ' 725 758 check fill "verbatim foo bar" <<-\EOF 726 759 url=cert:///path/to/cert.pem ··· 828 861 -c credential.with%0anewline.username=uh-oh \ 829 862 credential fill <stdin 2>stderr && 830 863 test_grep "skipping credential lookup for key" stderr 864 + ' 865 + 866 + BEL="$(printf '\007')" 867 + 868 + test_expect_success 'interactive prompt is sanitized' ' 869 + check fill cntrl-in-username <<-EOF 870 + protocol=https 871 + host=example.org 872 + -- 873 + protocol=https 874 + host=example.org 875 + username=${BEL}latrix Lestrange 876 + password=askpass-password 877 + -- 878 + askpass: Password for ${SQ}https://%07latrix%20Lestrange@example.org${SQ}: 879 + EOF 831 880 ' 832 881 833 882 test_done
+3 -3
t/t5541-http-push-smart.sh
··· 343 343 git push "$HTTPD_URL"/auth/smart/test_repo.git && 344 344 git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \ 345 345 log -1 --format=%s >actual && 346 - expect_askpass both user@host && 346 + expect_askpass both user%40host && 347 347 test_cmp expect actual 348 348 ' 349 349 ··· 355 355 git push "$HTTPD_URL"/auth-push/smart/test_repo.git && 356 356 git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \ 357 357 log -1 --format=%s >actual && 358 - expect_askpass both user@host && 358 + expect_askpass both user%40host && 359 359 test_cmp expect actual 360 360 ' 361 361 ··· 385 385 git push "$HTTPD_URL/half-auth-complete/smart/half-auth.git" && 386 386 git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/half-auth.git" \ 387 387 log -1 --format=%s >actual && 388 - expect_askpass both user@host && 388 + expect_askpass both user%40host && 389 389 test_cmp expect actual 390 390 ' 391 391
+7 -7
t/t5550-http-fetch-dumb.sh
··· 90 90 test_expect_success 'http auth can use just user in URL' ' 91 91 set_askpass wrong pass@host && 92 92 git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass && 93 - expect_askpass pass user@host 93 + expect_askpass pass user%40host 94 94 ' 95 95 96 96 test_expect_success 'http auth can request both user and pass' ' 97 97 set_askpass user@host pass@host && 98 98 git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both && 99 - expect_askpass both user@host 99 + expect_askpass both user%40host 100 100 ' 101 101 102 102 test_expect_success 'http auth respects credential helper config' ' ··· 114 114 test_config_global "credential.$HTTPD_URL.username" user@host && 115 115 set_askpass wrong pass@host && 116 116 git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user && 117 - expect_askpass pass user@host 117 + expect_askpass pass user%40host 118 118 ' 119 119 120 120 test_expect_success 'configured username does not override URL' ' 121 121 test_config_global "credential.$HTTPD_URL.username" wrong && 122 122 set_askpass wrong pass@host && 123 123 git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 && 124 - expect_askpass pass user@host 124 + expect_askpass pass user%40host 125 125 ' 126 126 127 127 test_expect_success 'set up repo with http submodules' ' ··· 142 142 set_askpass wrong pass@host && 143 143 git -c "credential.$HTTPD_URL.username=user@host" \ 144 144 clone --recursive super super-clone && 145 - expect_askpass pass user@host 145 + expect_askpass pass user%40host 146 146 ' 147 147 148 148 test_expect_success 'cmdline credential config passes submodule via fetch' ' ··· 153 153 git -C super-clone \ 154 154 -c "credential.$HTTPD_URL.username=user@host" \ 155 155 fetch --recurse-submodules && 156 - expect_askpass pass user@host 156 + expect_askpass pass user%40host 157 157 ' 158 158 159 159 test_expect_success 'cmdline credential config passes submodule update' ' ··· 170 170 git -C super-clone \ 171 171 -c "credential.$HTTPD_URL.username=user@host" \ 172 172 submodule update && 173 - expect_askpass pass user@host 173 + expect_askpass pass user%40host 174 174 ' 175 175 176 176 test_expect_success 'fetch changes via http' '
+8 -8
t/t5551-http-fetch-smart.sh
··· 181 181 echo two >expect && 182 182 set_askpass user@host pass@host && 183 183 git clone --bare "$HTTPD_URL/auth/smart/repo.git" smart-auth && 184 - expect_askpass both user@host && 184 + expect_askpass both user%40host && 185 185 git --git-dir=smart-auth log -1 --format=%s >actual && 186 186 test_cmp expect actual 187 187 ' ··· 199 199 echo two >expect && 200 200 set_askpass user@host pass@host && 201 201 git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth && 202 - expect_askpass both user@host && 202 + expect_askpass both user%40host && 203 203 git --git-dir=half-auth log -1 --format=%s >actual && 204 204 test_cmp expect actual 205 205 ' ··· 224 224 set_askpass user@host pass@host && 225 225 git -c credential.useHttpPath=true \ 226 226 clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth && 227 - expect_askpass both user@host auth/smart/repo.git 227 + expect_askpass both user%40host auth/smart/repo.git 228 228 ' 229 229 230 230 test_expect_success 'GIT_TRACE_CURL redacts auth details' ' 231 231 rm -rf redact-auth trace && 232 232 set_askpass user@host pass@host && 233 233 GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && 234 - expect_askpass both user@host && 234 + expect_askpass both user%40host && 235 235 236 236 # Ensure that there is no "Basic" followed by a base64 string, but that 237 237 # the auth details are redacted ··· 243 243 rm -rf redact-auth trace && 244 244 set_askpass user@host pass@host && 245 245 GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace && 246 - expect_askpass both user@host && 246 + expect_askpass both user%40host && 247 247 248 248 # Ensure that there is no "Basic" followed by a base64 string, but that 249 249 # the auth details are redacted ··· 256 256 set_askpass user@host pass@host && 257 257 GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \ 258 258 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && 259 - expect_askpass both user@host && 259 + expect_askpass both user%40host && 260 260 261 261 grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace 262 262 ' ··· 570 570 # the first request prompts the user... 571 571 set_askpass user@host pass@host && 572 572 git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null && 573 - expect_askpass both user@host && 573 + expect_askpass both user%40host && 574 574 575 575 # ...and the second one uses the stored value rather than 576 576 # prompting the user. ··· 601 601 # us to prompt the user again. 602 602 set_askpass user@host pass@host && 603 603 git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null && 604 - expect_askpass both user@host 604 + expect_askpass both user%40host 605 605 ' 606 606 607 607 test_expect_success 'client falls back from v2 to v0 to match server' '
+1 -1
t/t7300-clean.sh
··· 740 740 test_must_fail git clean -xdf 2>.git/err && 741 741 # grepping for a strerror string is unportable but it is OK here with 742 742 # MINGW prereq 743 - test_grep "too long" .git/err 743 + test_grep -e "too long" -e "No such file or directory" .git/err 744 744 ' 745 745 746 746 test_expect_success 'clean untracked paths by pathspec' '