{"thread":{"id":"66020","subject":"[PATCH 0/2] Some wincred fixes","startedAt":"2026-07-16T14:27:54Z","lastAt":"2026-07-16T18:35:02Z","messageCount":4,"participants":["Johannes Schindelin via GitGitGadget","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"548416","messageId":"pull.2182.git.1784212072.gitgitgadget@gmail.com","threadId":"66020","inReplyTo":null,"subject":"[PATCH 0/2] Some wincred fixes","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-16T14:27:49Z","receivedAt":"2026-07-16T14:27:54Z","isPatch":true,"body":"These were rolled out as part of the security fix release Git for Windows\nv2.55.0(3).\n\nJohannes Schindelin (2):\n  wincred: avoid memory corruption when erasing a credential\n  wincred: prevent silent credential loss when storing OAuth tokens\n\n contrib/credential/wincred/git-credential-wincred.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2182%2Fdscho%2Fwincred-fixes-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2182/dscho/wincred-fixes-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2182\n-- \ngitgitgadget\n"},{"id":"548417","messageId":"3ceda5ed3d8f56fc84b3794b9bce918271e22a32.1784212072.git.gitgitgadget@gmail.com","threadId":"66020","inReplyTo":"pull.2182.git.1784212072.gitgitgadget@gmail.com","subject":"[PATCH 1/2] wincred: avoid memory corruption when erasing a credential","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-16T14:27:50Z","receivedAt":"2026-07-16T14:27:55Z","isPatch":true,"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe earlier d22a488482 (wincred: avoid memory corruption, 2025-11-17)\nrepaired only get_credential(); match_cred_password() has the same\ndefect and is reached on `git credential reject`. When Git asks the\nhelper to erase a stored credential whose password was supplied by\nthe caller, the helper copies the candidate's password into a freshly\nallocated buffer for comparison. That copy overruns the allocation\nby one WCHAR of NUL, which on uninstrumented Windows manifests as\nprocess termination with status 0xC0000374. Because the helper can\ndie before reaching CredDeleteW(), `git credential reject` masks the\nfailure and the rejected credential remains stored.\n\nCredentialBlobSize is documented as a byte count, so for an N-WCHAR\nblob it equals N * sizeof(WCHAR). The pre-fix code allocated that\nmany bytes and asked wcsncpy_s to copy N wide characters, but\nwcsncpy_s always appends a terminating NUL WCHAR, writing one WCHAR\npast the allocation. The destination-capacity argument was also\npassed in bytes rather than in WCHAR elements as the API requires,\nso the safe-CRT runtime never rejected the copy.\n\nSee GHSA-rxqw-wxqg-g7hw.\n\nAssisted-by: Opus 4.7\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/credential/wincred/git-credential-wincred.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c\nindex 73c2b9b72a..190bbccdf9 100644\n--- a/contrib/credential/wincred/git-credential-wincred.c\n+++ b/contrib/credential/wincred/git-credential-wincred.c\n@@ -121,10 +121,10 @@ static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)\n \n static int match_cred_password(const CREDENTIALW *cred) {\n \tint ret;\n-\tWCHAR *cred_password = xmalloc(cred->CredentialBlobSize);\n-\twcsncpy_s(cred_password, cred->CredentialBlobSize,\n-\t\t(LPCWSTR)cred->CredentialBlob,\n-\t\tcred->CredentialBlobSize / sizeof(WCHAR));\n+\tsize_t wlen = cred->CredentialBlobSize / sizeof(WCHAR);\n+\tWCHAR *cred_password = xmalloc((wlen + 1) * sizeof(WCHAR));\n+\twcsncpy_s(cred_password, wlen + 1,\n+\t\t(LPCWSTR)cred->CredentialBlob, wlen);\n \tret = !wcscmp(cred_password, password);\n \tfree(cred_password);\n \treturn ret;\n-- \ngitgitgadget\n\n"},{"id":"548418","messageId":"2ec24be3b5a121736b5f4f7f6e5450d577208d21.1784212072.git.gitgitgadget@gmail.com","threadId":"66020","inReplyTo":"pull.2182.git.1784212072.gitgitgadget@gmail.com","subject":"[PATCH 2/2] wincred: prevent silent credential loss when storing OAuth tokens","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-16T14:27:51Z","receivedAt":"2026-07-16T14:27:57Z","isPatch":true,"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen `git credential approve` hands the wincred helper a password\ntogether with an `oauth_refresh_token`, the OAuth branch of\n`store_credential()` writes one WCHAR past the allocation while\nformatting both fields into a single `CredentialBlob`. On Windows\nthis trips heap verification and tears the helper down with status\n`0xC0000374`; `approve` masks the failure, so the credential the\nuser meant to save never reaches `CredWriteW()` and the next\nsession prompts for it again.\n\nThe bug has the same shape as the one fixed in the previous commit:\nthe allocation leaves no room for the terminating NUL, and the\n`sizeOfBuffer` argument to `_snwprintf_s()` is a byte count where\nthe API expects a WCHAR count, which lets the safe-CRT runtime\nwrite the terminator out of bounds.\n\nApply the same remedy d22a488482 (wincred: avoid memory corruption,\n2025-11-17) applied in `get_credential()`: allocate `(wlen + 1) *\nsizeof(WCHAR)` bytes and pass `wlen + 1` as the destination\ncapacity in WCHARs.\n\nThis closes the second of the two heap writes tracked under\nGHSA-rxqw-wxqg-g7hw.\n\nAssisted-by: Opus 4.7\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/credential/wincred/git-credential-wincred.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c\nindex 190bbccdf9..22eb27ca31 100644\n--- a/contrib/credential/wincred/git-credential-wincred.c\n+++ b/contrib/credential/wincred/git-credential-wincred.c\n@@ -208,8 +208,8 @@ static void store_credential(void)\n \n \tif (oauth_refresh_token) {\n \t\twlen = _scwprintf(L\"%s\\r\\noauth_refresh_token=%s\", password, oauth_refresh_token);\n-\t\tsecret = xmalloc(sizeof(WCHAR) * wlen);\n-\t\t_snwprintf_s(secret, sizeof(WCHAR) * wlen, wlen, L\"%s\\r\\noauth_refresh_token=%s\", password, oauth_refresh_token);\n+\t\tsecret = xmalloc((wlen + 1) * sizeof(WCHAR));\n+\t\t_snwprintf_s(secret, wlen + 1, wlen, L\"%s\\r\\noauth_refresh_token=%s\", password, oauth_refresh_token);\n \t} else {\n \t\tsecret = _wcsdup(password);\n \t}\n-- \ngitgitgadget\n"},{"id":"548445","messageId":"xmqqzezqg4vh.fsf@gitster.g","threadId":"66020","inReplyTo":"pull.2182.git.1784212072.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/2] Some wincred fixes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-16T18:34:58Z","receivedAt":"2026-07-16T18:35:02Z","isPatch":true,"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> These were rolled out as part of the security fix release Git for Windows\n> v2.55.0(3).\n\nThanks.  Let me merge it down fast.\n\n>\n> Johannes Schindelin (2):\n>   wincred: avoid memory corruption when erasing a credential\n>   wincred: prevent silent credential loss when storing OAuth tokens\n>\n>  contrib/credential/wincred/git-credential-wincred.c | 12 ++++++------\n>  1 file changed, 6 insertions(+), 6 deletions(-)\n>\n>\n> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2182%2Fdscho%2Fwincred-fixes-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2182/dscho/wincred-fixes-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2182\n"}]}