]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Sync with 2.31.2
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Thu, 17 Mar 2022 09:57:37 +0000 (10:57 +0100)
committerJohannes Schindelin <johannes.schindelin@gmx.de>
Wed, 23 Mar 2022 23:31:28 +0000 (00:31 +0100)
* maint-2.31:
  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

Documentation/RelNotes/2.30.3.txt [new file with mode: 0644]
Documentation/RelNotes/2.31.2.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/config/safe.txt [new file with mode: 0644]
compat/mingw.c
compat/mingw.h
git-compat-util.h
path.c
setup.c
t/t0060-path-utils.sh

diff --git a/Documentation/RelNotes/2.30.3.txt b/Documentation/RelNotes/2.30.3.txt
new file mode 100644 (file)
index 0000000..31b2a4d
--- /dev/null
@@ -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 (file)
index 0000000..aa13a5b
--- /dev/null
@@ -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.
index bf82766a6a272d3c42d93249ba64cf1a880088e1..a1efd744cd88d30e46bb31a5fd5efc2501d7d95b 100644 (file)
@@ -440,6 +440,8 @@ include::config/rerere.txt[]
 
 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 (file)
index 0000000..63597b2
--- /dev/null
@@ -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.
index aa647b367b0fab393f5309bbb1dac3627383b1e5..af1b8968fb61a011f59cdd05fbbc34cfe8c26425 100644 (file)
@@ -1,5 +1,6 @@
 #include "../git-compat-util.h"
 #include "win32.h"
+#include <aclapi.h>
 #include <conio.h>
 #include <wchar.h>
 #include "../strbuf.h"
@@ -1062,6 +1063,7 @@ int pipe(int filedes[2])
        return 0;
 }
 
+#ifndef __MINGW64__
 struct tm *gmtime_r(const time_t *timep, struct tm *result)
 {
        if (gmtime_s(result, timep) == 0)
@@ -1075,6 +1077,7 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
                return result;
        return NULL;
 }
+#endif
 
 char *mingw_getcwd(char *pointer, int len)
 {
@@ -2601,6 +2604,92 @@ static void setup_windows_environment(void)
        }
 }
 
+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;
index c9a52ad64a681fa9deff40c98d6ddabf842573de..ffa53a44b016596520ce701664dccac228b2cfb7 100644 (file)
@@ -453,6 +453,13 @@ char *mingw_query_user_email(void);
 #include <inttypes.h>
 #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.
  *
index a508dbe5a35104e6efb375c4a7bb81b2b62bd540..c1e9cf4f739d4212a047511dec943c1ec373e779 100644 (file)
 /* 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 @@ static inline int git_offset_1st_component(const char *path)
 #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 7bccd830e95890c16a93a52801c008a2b634ca24..94ef67389997658a1e4353e637bfbabd981bcc4d 100644 (file)
--- a/path.c
+++ b/path.c
@@ -1218,11 +1218,15 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes)
                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 59e2facd9db6710eecb16d58714d154000198c08..5c5ad90efccca4e0f3c9aa4f72c785564a974f69 100644 (file)
--- 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;
@@ -1024,6 +1025,42 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
        }
 }
 
+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,
@@ -1032,7 +1069,8 @@ enum discovery_result {
        /* 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
 };
 
 /*
@@ -1122,11 +1160,15 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
                }
                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;
                }
@@ -1253,6 +1295,19 @@ const char *setup_git_directory_gently(int *nongit_ok)
                            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
index de4960783f071a48fd136b14f7ac1051fa3c4b77..9e2219f37aa68f816cb37895846d49bca241d37f 100755 (executable)
@@ -55,12 +55,15 @@ fi
 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 4
 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)