git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 2/2] credential-cache--daemon: do not take over a running daemon's socket

From
Farid Zakaria <farid.m.zakaria@gmail.com>
Date
Nov 1, 2026, 23:29 UTC
Message-ID
<20261101-fz-credential-cache-race-v1-2-28f0bac51e11@gmail.com>
In-Reply-To
<20261101-fz-credential-cache-race-v1-0-28f0bac51e11@gmail.com>

When no daemon is running, two "git credential-cache store" run at the same time each try to start one. unix_stream_listen() removes whatever is at the socket path before it binds, so the second daemon takes over the socket of the first. The first daemon keeps running, but no client can reach it:

 - the credential its client stored is lost;
 - "git credential-cache exit" does not stop it, and it keeps the
   password in memory until it times out;
 - when it does exit, it removes the socket of the daemon that is
   still running, losing that daemon's credentials too.

The fix is to create the socket with unix_ss_create(), which the simple-ipc server already uses. It takes a lock and binds only if no other server is listening on the socket. A daemon that finds one prints "ok" and exits, and its client talks to the running daemon. Register the socket for removal only after binding it, so a daemon that loses never removes the other one's socket.

Signed-off-by: Farid Zakaria <farid.m.zakaria@gmail.com>
---
 builtin/credential-cache--daemon.c | 29 +++++++++++++++++++++--------
 t/t0301-credential-cache.sh        | 23 +++++++++++++++++++++++
 2 files changed, 44 insertions(+), 8 deletions(-)
diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c
index 0dfa11e2ec..de6720c276 100644
--- a/builtin/credential-cache--daemon.c
+++ b/builtin/credential-cache--daemon.c
@@ -11,6 +11,7 @@
 #include "tempfile.h"
 #include "credential.h"
 #include "unix-socket.h"
+#include "unix-stream-server.h"
 
 struct credential_cache_entry {
 	struct credential item;
@@ -231,12 +232,26 @@ static int serve_cache_loop(int fd)
 static void serve_cache(const char *socket_path, int debug)
 {
 	struct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;
-	int fd;
+	struct unix_ss_socket *server_socket;
+	struct tempfile *socket_file;
+	int ret;
 
-	fd = unix_stream_listen(socket_path, &opts);
-	if (fd < 0)
+	/*
+	 * Two clients may each start a daemon at the same time. Bind only
+	 * if no other daemon is listening yet; otherwise leave the socket
+	 * to that one, and our client will talk to it.
+	 */
+	ret = unix_ss_create(socket_path, &opts, -1, &server_socket);
+	if (ret == -2) {
+		printf("ok\n");
+		return;
+	}
+	if (ret < 0)
 		die_errno("unable to bind to '%s'", socket_path);
 
+	/* Remove the socket at exit only now that it is ours. */
+	socket_file = register_tempfile(socket_path);
+
 	printf("ok\n");
 	fclose(stdout);
 	if (!debug) {
@@ -244,10 +259,11 @@ static void serve_cache(const char *socket_path, int debug)
 			die_errno("unable to point stderr to /dev/null");
 	}
 
-	while (serve_cache_loop(fd))
+	while (serve_cache_loop(server_socket->fd_socket))
 		; /* nothing */
 
-	close(fd);
+	delete_tempfile(&socket_file);
+	unix_ss_free(server_socket);
 }
 
 static const char permissions_advice[] = N_(
@@ -303,7 +319,6 @@ int cmd_credential_cache_daemon(int argc,
 				const char *prefix,
 				struct repository *repo UNUSED)
 {
-	struct tempfile *socket_file;
 	const char *socket_path;
 	int ignore_sighup = 0;
 	static const char *usage[] = {
@@ -331,13 +346,11 @@ int cmd_credential_cache_daemon(int argc,
 		die("socket directory must be an absolute path");
 
 	init_socket_directory(socket_path);
-	socket_file = register_tempfile(socket_path);
 
 	if (ignore_sighup)
 		signal(SIGHUP, SIG_IGN);
 
 	serve_cache(socket_path, debug);
-	delete_tempfile(&socket_file);
 
 	return 0;
 }
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
index 6f7cfd9e33..8e07e55520 100755
--- a/t/t0301-credential-cache.sh
+++ b/t/t0301-credential-cache.sh
@@ -51,6 +51,29 @@ test_expect_success 'socket defaults to ~/.cache/git/credential/socket' '
 	test_path_is_socket "$HOME/.cache/git/credential/socket"
 '
 
+test_expect_success 'a second daemon does not take over the socket' '
+	test_when_finished "git credential-cache exit" &&
+	check approve cache <<-\EOF &&
+	protocol=https
+	host=example.com
+	username=store-user
+	password=store-pass
+	EOF
+	git credential-cache--daemon "$HOME/.cache/git/credential/socket" >out &&
+	echo ok >expect &&
+	test_cmp expect out &&
+	check fill cache <<-\EOF
+	protocol=https
+	host=example.com
+	--
+	protocol=https
+	host=example.com
+	username=store-user
+	password=store-pass
+	--
+	EOF
+'
+
 XDG_CACHE_HOME="$HOME/xdg"
 export XDG_CACHE_HOME
 # test behavior when XDG_CACHE_HOME is set
-- 
2.55.0
Previous: Jeff KingNext: Patrick Steinhardt
Message 6 of 14 in “credential-cache: fix two races when daemons start at once”
  1. 0/2 credential-cache: fix two races when daemons start at onceFarid Zakaria, Nov 1, 2026
  2. 1/2 credential-cache--daemon: allow a concurrently created socket dirFarid Zakaria, Nov 1, 2026
  3. Patrick SteinhardtNov 3, 2026
  4. Farid ZakariaNov 4, 2026
  5. Jeff KingNov 4, 2026
  6. 2/2 credential-cache--daemon: do not take over a running daemon's socketFarid Zakaria, Nov 1, 2026
  7. Patrick SteinhardtNov 3, 2026
  8. Farid ZakariaNov 4, 2026
  9. Junio C HamanoNov 3, 2026
  10. Jeff KingNov 4, 2026
  11. 0/3 credential-cache: fix two races when daemons start at onceFarid Zakaria, Nov 4, 2026
  12. 1/3 credential-cache--daemon: do not apply core.sharedRepositoryFarid Zakaria, Nov 4, 2026
  13. 2/3 credential-cache--daemon: allow a concurrently created socket dirFarid Zakaria, Nov 4, 2026
  14. 3/3 credential-cache--daemon: do not take over a running daemon's socketFarid Zakaria, Nov 4, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.