Sync with 2.32.1

* maint-2.32:
  Git 2.32.1
  Git 2.31.2
  Git 2.30.3
  setup_git_directory(): add an owner check for the top-level directory
  Add a function to determine whether a path is owned by the current user
diff --git a/Documentation/RelNotes/2.30.3.txt b/Documentation/RelNotes/2.30.3.txt
new file mode 100644
index 0000000..31b2a4d
--- /dev/null
+++ b/Documentation/RelNotes/2.30.3.txt
@@ -0,0 +1,24 @@
+Git v2.30.2 Release Notes
+=========================
+
+This release addresses the security issue CVE-2022-24765.
+
+Fixes since v2.30.2
+-------------------
+
+ * Build fix on Windows.
+
+ * Fix `GIT_CEILING_DIRECTORIES` with Windows-style root directories.
+
+ * CVE-2022-24765:
+   On multi-user machines, Git users might find themselves
+   unexpectedly in a Git worktree, e.g. when another user created a
+   repository in `C:\.git`, in a mounted network drive or in a
+   scratch space. Merely having a Git-aware prompt that runs `git
+   status` (or `git diff`) and navigating to a directory which is
+   supposedly not a Git worktree, or opening such a directory in an
+   editor or IDE such as VS Code or Atom, will potentially run
+   commands defined by that other user.
+
+Credit for finding this vulnerability goes to 俞晨东; The fix was
+authored by Johannes Schindelin.
diff --git a/Documentation/RelNotes/2.31.2.txt b/Documentation/RelNotes/2.31.2.txt
new file mode 100644
index 0000000..aa13a5b
--- /dev/null
+++ b/Documentation/RelNotes/2.31.2.txt
@@ -0,0 +1,6 @@
+Git v2.31.2 Release Notes
+=========================
+
+This release merges up the fixes that appear in v2.30.3 to address
+the security issue CVE-2022-24765; see the release notes for that
+version for details.
diff --git a/Documentation/RelNotes/2.32.1.txt b/Documentation/RelNotes/2.32.1.txt
new file mode 100644
index 0000000..7dcca13
--- /dev/null
+++ b/Documentation/RelNotes/2.32.1.txt
@@ -0,0 +1,6 @@
+Git v2.32.1 Release Notes
+=========================
+
+This release merges up the fixes that appear in v2.30.3 and
+v2.31.2 to address the security issue CVE-2022-24765; see the
+release notes for these versions for details.
diff --git a/Documentation/config.txt b/Documentation/config.txt
index bf82766..a1efd74 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -440,6 +440,8 @@
 
 include::config/reset.txt[]
 
+include::config/safe.txt[]
+
 include::config/sendemail.txt[]
 
 include::config/sequencer.txt[]
diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt
new file mode 100644
index 0000000..63597b2
--- /dev/null
+++ b/Documentation/config/safe.txt
@@ -0,0 +1,21 @@
+safe.directory::
+	These config entries specify Git-tracked directories that are
+	considered safe even if they are owned by someone other than the
+	current user. By default, Git will refuse to even parse a Git
+	config of a repository owned by someone else, let alone run its
+	hooks, and this config setting allows users to specify exceptions,
+	e.g. for intentionally shared repositories (see the `--shared`
+	option in linkgit:git-init[1]).
++
+This is a multi-valued setting, i.e. you can add more than one directory
+via `git config --add`. To reset the list of safe directories (e.g. to
+override any such directories specified in the system config), add a
+`safe.directory` entry with an empty value.
++
+This config setting is only respected when specified in a system or global
+config, not when it is specified in a repository config or via the command
+line option `-c safe.directory=<path>`.
++
+The value of this setting is interpolated, i.e. `~/<path>` expands to a
+path relative to the home directory and `%(prefix)/<path>` expands to a
+path relative to Git's (runtime) prefix.
diff --git a/compat/mingw.c b/compat/mingw.c
index 9e0cd1e..5e42191 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1,5 +1,6 @@
 #include "../git-compat-util.h"
 #include "win32.h"
+#include <aclapi.h>
 #include <conio.h>
 #include <wchar.h>
 #include "../strbuf.h"
@@ -1083,6 +1084,7 @@
 	return 0;
 }
 
+#ifndef __MINGW64__
 struct tm *gmtime_r(const time_t *timep, struct tm *result)
 {
 	if (gmtime_s(result, timep) == 0)
@@ -1096,6 +1098,7 @@
 		return result;
 	return NULL;
 }
+#endif
 
 char *mingw_getcwd(char *pointer, int len)
 {
@@ -2622,6 +2625,92 @@
 	}
 }
 
+static PSID get_current_user_sid(void)
+{
+	HANDLE token;
+	DWORD len = 0;
+	PSID result = NULL;
+
+	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
+		return NULL;
+
+	if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
+		TOKEN_USER *info = xmalloc((size_t)len);
+		if (GetTokenInformation(token, TokenUser, info, len, &len)) {
+			len = GetLengthSid(info->User.Sid);
+			result = xmalloc(len);
+			if (!CopySid(len, result, info->User.Sid)) {
+				error(_("failed to copy SID (%ld)"),
+				      GetLastError());
+				FREE_AND_NULL(result);
+			}
+		}
+		FREE_AND_NULL(info);
+	}
+	CloseHandle(token);
+
+	return result;
+}
+
+int is_path_owned_by_current_sid(const char *path)
+{
+	WCHAR wpath[MAX_PATH];
+	PSID sid = NULL;
+	PSECURITY_DESCRIPTOR descriptor = NULL;
+	DWORD err;
+
+	static wchar_t home[MAX_PATH];
+
+	int result = 0;
+
+	if (xutftowcs_path(wpath, path) < 0)
+		return 0;
+
+	/*
+	 * On Windows, the home directory is owned by the administrator, but for
+	 * all practical purposes, it belongs to the user. Do pretend that it is
+	 * owned by the user.
+	 */
+	if (!*home) {
+		DWORD size = ARRAY_SIZE(home);
+		DWORD len = GetEnvironmentVariableW(L"HOME", home, size);
+		if (!len || len > size)
+			wcscpy(home, L"::N/A::");
+	}
+	if (!wcsicmp(wpath, home))
+		return 1;
+
+	/* Get the owner SID */
+	err = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT,
+				    OWNER_SECURITY_INFORMATION |
+				    DACL_SECURITY_INFORMATION,
+				    &sid, NULL, NULL, NULL, &descriptor);
+
+	if (err != ERROR_SUCCESS)
+		error(_("failed to get owner for '%s' (%ld)"), path, err);
+	else if (sid && IsValidSid(sid)) {
+		/* Now, verify that the SID matches the current user's */
+		static PSID current_user_sid;
+
+		if (!current_user_sid)
+			current_user_sid = get_current_user_sid();
+
+		if (current_user_sid &&
+		    IsValidSid(current_user_sid) &&
+		    EqualSid(sid, current_user_sid))
+			result = 1;
+	}
+
+	/*
+	 * We can release the security descriptor struct only now because `sid`
+	 * actually points into this struct.
+	 */
+	if (descriptor)
+		LocalFree(descriptor);
+
+	return result;
+}
+
 int is_valid_win32_path(const char *path, int allow_literal_nul)
 {
 	const char *p = path;
diff --git a/compat/mingw.h b/compat/mingw.h
index c9a52ad..ffa53a4 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -454,6 +454,13 @@
 #endif
 
 /**
+ * Verifies that the specified path is owned by the user running the
+ * current process.
+ */
+int is_path_owned_by_current_sid(const char *path);
+#define is_path_owned_by_current_user is_path_owned_by_current_sid
+
+/**
  * Verifies that the given path is a valid one on Windows.
  *
  * In particular, path segments are disallowed which
diff --git a/git-compat-util.h b/git-compat-util.h
index b466053..dc52aea 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -127,7 +127,9 @@
 /* Approximation of the length of the decimal representation of this type. */
 #define decimal_length(x)	((int)(sizeof(x) * 2.56 + 0.5) + 1)
 
-#if defined(__sun__)
+#ifdef __MINGW64__
+#define _POSIX_C_SOURCE 1
+#elif defined(__sun__)
  /*
   * On Solaris, when _XOPEN_EXTENDED is set, its header file
   * forces the programs to be XPG4v2, defeating any _XOPEN_SOURCE
@@ -395,6 +397,18 @@
 #define is_valid_path(path) 1
 #endif
 
+#ifndef is_path_owned_by_current_user
+static inline int is_path_owned_by_current_uid(const char *path)
+{
+	struct stat st;
+	if (lstat(path, &st))
+		return 0;
+	return st.st_uid == geteuid();
+}
+
+#define is_path_owned_by_current_user is_path_owned_by_current_uid
+#endif
+
 #ifndef find_last_dir_sep
 static inline char *git_find_last_dir_sep(const char *path)
 {
diff --git a/path.c b/path.c
index 7bccd83..94ef673 100644
--- a/path.c
+++ b/path.c
@@ -1218,11 +1218,15 @@
 		const char *ceil = prefixes->items[i].string;
 		int len = strlen(ceil);
 
-		if (len == 1 && ceil[0] == '/')
-			len = 0; /* root matches anything, with length 0 */
-		else if (!strncmp(path, ceil, len) && path[len] == '/')
-			; /* match of length len */
-		else
+		/*
+		 * For root directories (`/`, `C:/`, `//server/share/`)
+		 * adjust the length to exclude the trailing slash.
+		 */
+		if (len > 0 && ceil[len - 1] == '/')
+			len--;
+
+		if (strncmp(path, ceil, len) ||
+		    path[len] != '/' || !path[len + 1])
 			continue; /* no match */
 
 		if (len > max_len)
diff --git a/setup.c b/setup.c
index 347d718..2ed94ba 100644
--- a/setup.c
+++ b/setup.c
@@ -5,6 +5,7 @@
 #include "string-list.h"
 #include "chdir-notify.h"
 #include "promisor-remote.h"
+#include "quote.h"
 
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
@@ -1025,6 +1026,42 @@
 	}
 }
 
+struct safe_directory_data {
+	const char *path;
+	int is_safe;
+};
+
+static int safe_directory_cb(const char *key, const char *value, void *d)
+{
+	struct safe_directory_data *data = d;
+
+	if (!value || !*value)
+		data->is_safe = 0;
+	else {
+		const char *interpolated = NULL;
+
+		if (!git_config_pathname(&interpolated, key, value) &&
+		    !fspathcmp(data->path, interpolated ? interpolated : value))
+			data->is_safe = 1;
+
+		free((char *)interpolated);
+	}
+
+	return 0;
+}
+
+static int ensure_valid_ownership(const char *path)
+{
+	struct safe_directory_data data = { .path = path };
+
+	if (is_path_owned_by_current_user(path))
+		return 1;
+
+	read_very_early_config(safe_directory_cb, &data);
+
+	return data.is_safe;
+}
+
 enum discovery_result {
 	GIT_DIR_NONE = 0,
 	GIT_DIR_EXPLICIT,
@@ -1033,7 +1070,8 @@
 	/* these are errors */
 	GIT_DIR_HIT_CEILING = -1,
 	GIT_DIR_HIT_MOUNT_POINT = -2,
-	GIT_DIR_INVALID_GITFILE = -3
+	GIT_DIR_INVALID_GITFILE = -3,
+	GIT_DIR_INVALID_OWNERSHIP = -4
 };
 
 /*
@@ -1123,11 +1161,15 @@
 		}
 		strbuf_setlen(dir, offset);
 		if (gitdirenv) {
+			if (!ensure_valid_ownership(dir->buf))
+				return GIT_DIR_INVALID_OWNERSHIP;
 			strbuf_addstr(gitdir, gitdirenv);
 			return GIT_DIR_DISCOVERED;
 		}
 
 		if (is_git_directory(dir->buf)) {
+			if (!ensure_valid_ownership(dir->buf))
+				return GIT_DIR_INVALID_OWNERSHIP;
 			strbuf_addstr(gitdir, ".");
 			return GIT_DIR_BARE;
 		}
@@ -1259,6 +1301,19 @@
 			    dir.buf);
 		*nongit_ok = 1;
 		break;
+	case GIT_DIR_INVALID_OWNERSHIP:
+		if (!nongit_ok) {
+			struct strbuf quoted = STRBUF_INIT;
+
+			sq_quote_buf_pretty(&quoted, dir.buf);
+			die(_("unsafe repository ('%s' is owned by someone else)\n"
+			      "To add an exception for this directory, call:\n"
+			      "\n"
+			      "\tgit config --global --add safe.directory %s"),
+			    dir.buf, quoted.buf);
+		}
+		*nongit_ok = 1;
+		break;
 	case GIT_DIR_NONE:
 		/*
 		 * As a safeguard against setup_git_directory_gently_1 returning
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index de49607..9e2219f 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -55,12 +55,15 @@
 ancestor() {
 	# We do some math with the expected ancestor length.
 	expected=$3
-	if test -n "$rootoff" && test "x$expected" != x-1; then
-		expected=$(($expected-$rootslash))
-		test $expected -lt 0 ||
-		expected=$(($expected+$rootoff))
-	fi
-	test_expect_success "longest ancestor: $1 $2 => $expected" \
+	case "$rootoff,$expected,$2" in
+	*,*,//*) ;; # leave UNC paths alone
+	[0-9]*,[0-9]*,/*)
+		# On Windows, expect MSYS2 pseudo root translation for
+		# Unix-style absolute paths
+		expected=$(($expected-$rootslash+$rootoff))
+		;;
+	esac
+	test_expect_success $4 "longest ancestor: $1 $2 => $expected" \
 	"actual=\$(test-tool path-utils longest_ancestor_length '$1' '$2') &&
 	 test \"\$actual\" = '$expected'"
 }
@@ -156,6 +159,11 @@
 ancestor /foo/bar /foo:/bar 4
 ancestor /foo/bar /bar -1
 
+# Windows-specific: DOS drives, network shares
+ancestor C:/Users/me C:/ 2 MINGW
+ancestor D:/Users/me C:/ -1 MINGW
+ancestor //server/share/my-directory //server/share/ 14 MINGW
+
 test_expect_success 'strip_path_suffix' '
 	test c:/msysgit = $(test-tool path-utils strip_path_suffix \
 		c:/msysgit/libexec//git-core libexec/git-core)