[PATCH] wincred: fix line split of secret blob content

DORMANTno replies

From: Marc Becker via GitGitGadget <hidden>
Date: 2026-10-09 13:45:21
Subsystem: the rest · Maintainer: Linus Torvalds

From: Marc Becker <redacted>

operate on immutable blob data (wcsncpy_s still had invalid target size)
split on newline character to avoid bleed-over on multi-line content

Signed-off-by: Marc Becker <redacted>
---
    wincred: fix line split of secret blob content

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2251%2Fbecm%2Ffix-wincred-secret-linesplit-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2251/becm/fix-wincred-secret-linesplit-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2251

 .../wincred/git-credential-wincred.c          | 86 ++++++++++++-------
 1 file changed, 55 insertions(+), 31 deletions(-)
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index 22eb27ca31..584f457774 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -6,6 +6,7 @@
 #include <stdio.h>
 #include <io.h>
 #include <fcntl.h>
+#include <wchar.h>
 #include <wincred.h>
 
 /* common helpers */
@@ -148,51 +149,74 @@ static void get_credential(void)
 {
 	CREDENTIALW **creds;
 	DWORD num_creds;
-	int i;
-	CREDENTIAL_ATTRIBUTEW *attr;
-	WCHAR *secret;
-	WCHAR *line;
-	WCHAR *remaining_lines;
-	WCHAR *part;
-	WCHAR *remaining_parts;
 
 	if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
 		return;
 
-	/* search for the first credential that matches username */
-	for (i = 0; i < num_creds; ++i)
+	/* search for the first credential that matches target and username */
+	for (int i = 0; i < num_creds; ++i) {
 		if (match_cred(creds[i], 0)) {
-			write_item("username", creds[i]->UserName,
-				creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
-			if (creds[i]->CredentialBlobSize > 0) {
-				secret = xmalloc(creds[i]->CredentialBlobSize + sizeof(WCHAR));
-				wcsncpy_s(secret, creds[i]->CredentialBlobSize, (LPCWSTR)creds[i]->CredentialBlob, creds[i]->CredentialBlobSize / sizeof(WCHAR));
-				line = wcstok_s(secret, L"\r\n", &remaining_lines);
-				write_item("password", line, line ? wcslen(line) : 0);
-				while(line != NULL) {
-					part = wcstok_s(line, L"=", &remaining_parts);
-					if (!wcscmp(part, L"oauth_refresh_token")) {
-						write_item("oauth_refresh_token", remaining_parts, remaining_parts ? wcslen(remaining_parts) : 0);
-					}
-					line = wcstok_s(NULL, L"\r\n", &remaining_lines);
-				}
-				free(secret);
+			LPCWSTR username = creds[i]->UserName;
+			LPCWSTR blob = (LPCWSTR)creds[i]->CredentialBlob;
+			LPCWSTR end;
+			DWORD wlen;
+
+			write_item("username", username, username ? wcslen(username) : 0);
+
+			wlen = creds[i]->CredentialBlobSize / sizeof(WCHAR);
+
+			// check if content is single line
+			if ((end = wmemchr(blob, '\n', wlen)) == NULL) {
+				write_item("password", blob, wlen);
 			} else {
-				write_item("password",
-						(LPCWSTR)creds[i]->CredentialBlob,
-						creds[i]->CredentialBlobSize / sizeof(WCHAR));
+				DWORD length = end++ - blob;
+
+				// correct remaining size and drop carriage return at line end
+				wlen -= length + 1;
+				if (length && blob[length - 1] == '\r') {
+					--length;
+				}
+				write_item("password", blob, length);
+
+				// key/value content starting on next line
+				blob = end;
+				do {
+					LPCWSTR value;
+
+					// find line end
+					if ((end = wmemchr(blob, '\n', wlen)) == NULL) {
+						length = wlen;
+					} else {
+						length = end++ - blob;
+						// correct remaining size and drop carriage return at line end
+						wlen -= length + 1;
+						if (length && blob[length - 1] == '\r') {
+							--length;
+						}
+					}
+					// find key/value separator for extended credential info
+					if ((value = wmemchr(blob, '=', length)) != NULL) {
+						static const LPCWSTR refresh = L"oauth_refresh_token";
+						DWORD klen = value - blob;
+
+						// write entries known to git credential protocol
+						if (klen == wcslen(refresh) && memcmp(blob, refresh, klen) == 0) {
+							write_item("oauth_refresh_token", value + 1, length - klen - 1);
+						}
+					}
+				} while ((blob = end));
 			}
 			for (int j = 0; j < creds[i]->AttributeCount; j++) {
-				attr = creds[i]->Attributes + j;
+				CREDENTIAL_ATTRIBUTEW *attr = creds[i]->Attributes + j;
+
 				if (!wcscmp(attr->Keyword, L"git_password_expiry_utc")) {
-					write_item("password_expiry_utc", (LPCWSTR)attr->Value,
-					attr->ValueSize / sizeof(WCHAR));
+					write_item("password_expiry_utc", (LPCWSTR)attr->Value, attr->ValueSize / sizeof(WCHAR));
 					break;
 				}
 			}
 			break;
 		}
-
+	}
 	CredFree(creds);
 }
 
base-commit: 6de20f6092dcf9bdb1c8efe03db4b70c82b423dd
-- 
gitgitgadget
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help