diff options
Diffstat (limited to 'debian/patches/CVE-2022-24765.patch')
-rw-r--r-- | debian/patches/CVE-2022-24765.patch | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/debian/patches/CVE-2022-24765.patch b/debian/patches/CVE-2022-24765.patch new file mode 100644 index 0000000..f62a327 --- /dev/null +++ b/debian/patches/CVE-2022-24765.patch @@ -0,0 +1,499 @@ +Origin: https://github.com/git/git/commit/6e7ad1e4c22e7038975ba37c7413374fe566b064 +Origin: https://github.com/git/git/commit/bdc77d1d685be9c10b88abb281a42bc620548595 +Origin: https://github.com/git/git/commit/8959555cee7ec045958f9b6dd62e541affb7e7d9 +Origin: https://github.com/git/git/commit/cb95038137e9e66fc6a6b4a0e8db62bcc521b709 +Origin: https://github.com/git/git/commit/e47363e5a8bdf5144059d664c45c0975243ef05b +Origin: https://github.com/git/git/commit/bb50ec3cc300eeff3aba7a2bea145aabdb477d31 +Origin: https://github.com/git/git/commit/0f85c4a30b072a26d74af8bbf63cc8f6a5dfc1b8 +Reviewed-by: Aron Xu <aron@debian.org> +Last-Updated: 2023-01-26 +Comment: With the above patches, we are effectively updating the package to v2.30.3 + plus some more changes. The symlink change to RelNotes has been dropped. + +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/config.txt b/Documentation/config.txt +index 6ba50b1..34e6d47 100644 +--- a/Documentation/config.txt ++++ b/Documentation/config.txt +@@ -438,6 +438,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 +index 0000000..6d764fe +--- /dev/null ++++ b/Documentation/config/safe.txt +@@ -0,0 +1,28 @@ ++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. +++ ++To completely opt-out of this security check, set `safe.directory` to the ++string `*`. This will allow all repositories to be treated as if their ++directory was listed in the `safe.directory` list. If `safe.directory=*` ++is set in system config and you want to re-enable this protection, then ++initialize your list with an empty value before listing the repositories ++that you deem safe. +diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN +index 9d789e0..7cf68be 100755 +--- a/GIT-VERSION-GEN ++++ b/GIT-VERSION-GEN +@@ -1,7 +1,7 @@ + #!/bin/sh + + GVF=GIT-VERSION-FILE +-DEF_VER=v2.30.2 ++DEF_VER=v2.30.3 + + LF=' + ' +diff --git a/compat/mingw.c b/compat/mingw.c +index a435998..38ac359 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" +@@ -1060,6 +1061,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) +@@ -1073,6 +1075,7 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) + return result; + return NULL; + } ++#endif + + char *mingw_getcwd(char *pointer, int len) + { +@@ -2599,6 +2602,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; +diff --git a/compat/mingw.h b/compat/mingw.h +index af8eddd..f6bab54 100644 +--- a/compat/mingw.h ++++ b/compat/mingw.h +@@ -452,6 +452,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. + * +diff --git a/git-compat-util.h b/git-compat-util.h +index 7d3db43..63ba89d 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 +@@ -390,6 +392,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 7b385e5..853e716 100644 +--- 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 c04cd25..aad9ace 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; +@@ -1024,6 +1025,48 @@ 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 (strcmp(key, "safe.directory")) ++ return 0; ++ ++ if (!value || !*value) { ++ data->is_safe = 0; ++ } else if (!strcmp(value, "*")) { ++ data->is_safe = 1; ++ } 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 (!git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0) && ++ 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 +1075,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 +1166,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 +1301,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("ed, 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/t0033-safe-directory.sh b/t/t0033-safe-directory.sh +new file mode 100755 +index 0000000..239d93f +--- /dev/null ++++ b/t/t0033-safe-directory.sh +@@ -0,0 +1,49 @@ ++#!/bin/sh ++ ++test_description='verify safe.directory checks' ++ ++. ./test-lib.sh ++ ++GIT_TEST_ASSUME_DIFFERENT_OWNER=1 ++export GIT_TEST_ASSUME_DIFFERENT_OWNER ++ ++expect_rejected_dir () { ++ test_must_fail git status 2>err && ++ grep "safe.directory" err ++} ++ ++test_expect_success 'safe.directory is not set' ' ++ expect_rejected_dir ++' ++ ++test_expect_success 'safe.directory does not match' ' ++ git config --global safe.directory bogus && ++ expect_rejected_dir ++' ++ ++test_expect_success 'path exist as different key' ' ++ git config --global foo.bar "$(pwd)" && ++ expect_rejected_dir ++' ++ ++test_expect_success 'safe.directory matches' ' ++ git config --global --add safe.directory "$(pwd)" && ++ git status ++' ++ ++test_expect_success 'safe.directory matches, but is reset' ' ++ git config --global --add safe.directory "" && ++ expect_rejected_dir ++' ++ ++test_expect_success 'safe.directory=*' ' ++ git config --global --add safe.directory "*" && ++ git status ++' ++ ++test_expect_success 'safe.directory=*, but is reset' ' ++ git config --global --add safe.directory "" && ++ expect_rejected_dir ++' ++ ++test_done +diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh +index 56db5c8..f538264 100755 +--- a/t/t0060-path-utils.sh ++++ b/t/t0060-path-utils.sh +@@ -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) |