зеркало из https://github.com/microsoft/git.git
Sync with 2.30.3
* maint-2.30: 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
This commit is contained in:
Коммит
6a2381a3e5
|
@ -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.
|
|
@ -440,6 +440,8 @@ include::config/rerere.txt[]
|
|||
|
||||
include::config/reset.txt[]
|
||||
|
||||
include::config/safe.txt[]
|
||||
|
||||
include::config/sendemail.txt[]
|
||||
|
||||
include::config/sequencer.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.
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
14
path.c
14
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)
|
||||
|
|
57
setup.c
57
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("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
|
||||
|
|
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче