Skip to content

Commit 6a2381a

Browse files
committed
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
2 parents 48bf2fa + cb95038 commit 6a2381a

File tree

9 files changed

+237
-13
lines changed

9 files changed

+237
-13
lines changed

Documentation/RelNotes/2.30.3.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Git v2.30.2 Release Notes
2+
=========================
3+
4+
This release addresses the security issue CVE-2022-24765.
5+
6+
Fixes since v2.30.2
7+
-------------------
8+
9+
* Build fix on Windows.
10+
11+
* Fix `GIT_CEILING_DIRECTORIES` with Windows-style root directories.
12+
13+
* CVE-2022-24765:
14+
On multi-user machines, Git users might find themselves
15+
unexpectedly in a Git worktree, e.g. when another user created a
16+
repository in `C:\.git`, in a mounted network drive or in a
17+
scratch space. Merely having a Git-aware prompt that runs `git
18+
status` (or `git diff`) and navigating to a directory which is
19+
supposedly not a Git worktree, or opening such a directory in an
20+
editor or IDE such as VS Code or Atom, will potentially run
21+
commands defined by that other user.
22+
23+
Credit for finding this vulnerability goes to 俞晨东; The fix was
24+
authored by Johannes Schindelin.

Documentation/config.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,8 @@ include::config/rerere.txt[]
440440

441441
include::config/reset.txt[]
442442

443+
include::config/safe.txt[]
444+
443445
include::config/sendemail.txt[]
444446

445447
include::config/sequencer.txt[]

Documentation/config/safe.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
safe.directory::
2+
These config entries specify Git-tracked directories that are
3+
considered safe even if they are owned by someone other than the
4+
current user. By default, Git will refuse to even parse a Git
5+
config of a repository owned by someone else, let alone run its
6+
hooks, and this config setting allows users to specify exceptions,
7+
e.g. for intentionally shared repositories (see the `--shared`
8+
option in linkgit:git-init[1]).
9+
+
10+
This is a multi-valued setting, i.e. you can add more than one directory
11+
via `git config --add`. To reset the list of safe directories (e.g. to
12+
override any such directories specified in the system config), add a
13+
`safe.directory` entry with an empty value.
14+
+
15+
This config setting is only respected when specified in a system or global
16+
config, not when it is specified in a repository config or via the command
17+
line option `-c safe.directory=<path>`.
18+
+
19+
The value of this setting is interpolated, i.e. `~/<path>` expands to a
20+
path relative to the home directory and `%(prefix)/<path>` expands to a
21+
path relative to Git's (runtime) prefix.

compat/mingw.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "../git-compat-util.h"
22
#include "win32.h"
3+
#include <aclapi.h>
34
#include <conio.h>
45
#include <wchar.h>
56
#include "../strbuf.h"
@@ -1060,6 +1061,7 @@ int pipe(int filedes[2])
10601061
return 0;
10611062
}
10621063

1064+
#ifndef __MINGW64__
10631065
struct tm *gmtime_r(const time_t *timep, struct tm *result)
10641066
{
10651067
if (gmtime_s(result, timep) == 0)
@@ -1073,6 +1075,7 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
10731075
return result;
10741076
return NULL;
10751077
}
1078+
#endif
10761079

10771080
char *mingw_getcwd(char *pointer, int len)
10781081
{
@@ -2599,6 +2602,92 @@ static void setup_windows_environment(void)
25992602
}
26002603
}
26012604

2605+
static PSID get_current_user_sid(void)
2606+
{
2607+
HANDLE token;
2608+
DWORD len = 0;
2609+
PSID result = NULL;
2610+
2611+
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
2612+
return NULL;
2613+
2614+
if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
2615+
TOKEN_USER *info = xmalloc((size_t)len);
2616+
if (GetTokenInformation(token, TokenUser, info, len, &len)) {
2617+
len = GetLengthSid(info->User.Sid);
2618+
result = xmalloc(len);
2619+
if (!CopySid(len, result, info->User.Sid)) {
2620+
error(_("failed to copy SID (%ld)"),
2621+
GetLastError());
2622+
FREE_AND_NULL(result);
2623+
}
2624+
}
2625+
FREE_AND_NULL(info);
2626+
}
2627+
CloseHandle(token);
2628+
2629+
return result;
2630+
}
2631+
2632+
int is_path_owned_by_current_sid(const char *path)
2633+
{
2634+
WCHAR wpath[MAX_PATH];
2635+
PSID sid = NULL;
2636+
PSECURITY_DESCRIPTOR descriptor = NULL;
2637+
DWORD err;
2638+
2639+
static wchar_t home[MAX_PATH];
2640+
2641+
int result = 0;
2642+
2643+
if (xutftowcs_path(wpath, path) < 0)
2644+
return 0;
2645+
2646+
/*
2647+
* On Windows, the home directory is owned by the administrator, but for
2648+
* all practical purposes, it belongs to the user. Do pretend that it is
2649+
* owned by the user.
2650+
*/
2651+
if (!*home) {
2652+
DWORD size = ARRAY_SIZE(home);
2653+
DWORD len = GetEnvironmentVariableW(L"HOME", home, size);
2654+
if (!len || len > size)
2655+
wcscpy(home, L"::N/A::");
2656+
}
2657+
if (!wcsicmp(wpath, home))
2658+
return 1;
2659+
2660+
/* Get the owner SID */
2661+
err = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT,
2662+
OWNER_SECURITY_INFORMATION |
2663+
DACL_SECURITY_INFORMATION,
2664+
&sid, NULL, NULL, NULL, &descriptor);
2665+
2666+
if (err != ERROR_SUCCESS)
2667+
error(_("failed to get owner for '%s' (%ld)"), path, err);
2668+
else if (sid && IsValidSid(sid)) {
2669+
/* Now, verify that the SID matches the current user's */
2670+
static PSID current_user_sid;
2671+
2672+
if (!current_user_sid)
2673+
current_user_sid = get_current_user_sid();
2674+
2675+
if (current_user_sid &&
2676+
IsValidSid(current_user_sid) &&
2677+
EqualSid(sid, current_user_sid))
2678+
result = 1;
2679+
}
2680+
2681+
/*
2682+
* We can release the security descriptor struct only now because `sid`
2683+
* actually points into this struct.
2684+
*/
2685+
if (descriptor)
2686+
LocalFree(descriptor);
2687+
2688+
return result;
2689+
}
2690+
26022691
int is_valid_win32_path(const char *path, int allow_literal_nul)
26032692
{
26042693
const char *p = path;

compat/mingw.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,13 @@ char *mingw_query_user_email(void);
453453
#include <inttypes.h>
454454
#endif
455455

456+
/**
457+
* Verifies that the specified path is owned by the user running the
458+
* current process.
459+
*/
460+
int is_path_owned_by_current_sid(const char *path);
461+
#define is_path_owned_by_current_user is_path_owned_by_current_sid
462+
456463
/**
457464
* Verifies that the given path is a valid one on Windows.
458465
*

git-compat-util.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@
127127
/* Approximation of the length of the decimal representation of this type. */
128128
#define decimal_length(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
129129

130-
#if defined(__sun__)
130+
#ifdef __MINGW64__
131+
#define _POSIX_C_SOURCE 1
132+
#elif defined(__sun__)
131133
/*
132134
* On Solaris, when _XOPEN_EXTENDED is set, its header file
133135
* forces the programs to be XPG4v2, defeating any _XOPEN_SOURCE
@@ -390,6 +392,18 @@ static inline int git_offset_1st_component(const char *path)
390392
#define is_valid_path(path) 1
391393
#endif
392394

395+
#ifndef is_path_owned_by_current_user
396+
static inline int is_path_owned_by_current_uid(const char *path)
397+
{
398+
struct stat st;
399+
if (lstat(path, &st))
400+
return 0;
401+
return st.st_uid == geteuid();
402+
}
403+
404+
#define is_path_owned_by_current_user is_path_owned_by_current_uid
405+
#endif
406+
393407
#ifndef find_last_dir_sep
394408
static inline char *git_find_last_dir_sep(const char *path)
395409
{

path.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,11 +1218,15 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes)
12181218
const char *ceil = prefixes->items[i].string;
12191219
int len = strlen(ceil);
12201220

1221-
if (len == 1 && ceil[0] == '/')
1222-
len = 0; /* root matches anything, with length 0 */
1223-
else if (!strncmp(path, ceil, len) && path[len] == '/')
1224-
; /* match of length len */
1225-
else
1221+
/*
1222+
* For root directories (`/`, `C:/`, `//server/share/`)
1223+
* adjust the length to exclude the trailing slash.
1224+
*/
1225+
if (len > 0 && ceil[len - 1] == '/')
1226+
len--;
1227+
1228+
if (strncmp(path, ceil, len) ||
1229+
path[len] != '/' || !path[len + 1])
12261230
continue; /* no match */
12271231

12281232
if (len > max_len)

setup.c

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "string-list.h"
66
#include "chdir-notify.h"
77
#include "promisor-remote.h"
8+
#include "quote.h"
89

910
static int inside_git_dir = -1;
1011
static int inside_work_tree = -1;
@@ -1024,6 +1025,42 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
10241025
}
10251026
}
10261027

1028+
struct safe_directory_data {
1029+
const char *path;
1030+
int is_safe;
1031+
};
1032+
1033+
static int safe_directory_cb(const char *key, const char *value, void *d)
1034+
{
1035+
struct safe_directory_data *data = d;
1036+
1037+
if (!value || !*value)
1038+
data->is_safe = 0;
1039+
else {
1040+
const char *interpolated = NULL;
1041+
1042+
if (!git_config_pathname(&interpolated, key, value) &&
1043+
!fspathcmp(data->path, interpolated ? interpolated : value))
1044+
data->is_safe = 1;
1045+
1046+
free((char *)interpolated);
1047+
}
1048+
1049+
return 0;
1050+
}
1051+
1052+
static int ensure_valid_ownership(const char *path)
1053+
{
1054+
struct safe_directory_data data = { .path = path };
1055+
1056+
if (is_path_owned_by_current_user(path))
1057+
return 1;
1058+
1059+
read_very_early_config(safe_directory_cb, &data);
1060+
1061+
return data.is_safe;
1062+
}
1063+
10271064
enum discovery_result {
10281065
GIT_DIR_NONE = 0,
10291066
GIT_DIR_EXPLICIT,
@@ -1032,7 +1069,8 @@ enum discovery_result {
10321069
/* these are errors */
10331070
GIT_DIR_HIT_CEILING = -1,
10341071
GIT_DIR_HIT_MOUNT_POINT = -2,
1035-
GIT_DIR_INVALID_GITFILE = -3
1072+
GIT_DIR_INVALID_GITFILE = -3,
1073+
GIT_DIR_INVALID_OWNERSHIP = -4
10361074
};
10371075

10381076
/*
@@ -1122,11 +1160,15 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
11221160
}
11231161
strbuf_setlen(dir, offset);
11241162
if (gitdirenv) {
1163+
if (!ensure_valid_ownership(dir->buf))
1164+
return GIT_DIR_INVALID_OWNERSHIP;
11251165
strbuf_addstr(gitdir, gitdirenv);
11261166
return GIT_DIR_DISCOVERED;
11271167
}
11281168

11291169
if (is_git_directory(dir->buf)) {
1170+
if (!ensure_valid_ownership(dir->buf))
1171+
return GIT_DIR_INVALID_OWNERSHIP;
11301172
strbuf_addstr(gitdir, ".");
11311173
return GIT_DIR_BARE;
11321174
}
@@ -1253,6 +1295,19 @@ const char *setup_git_directory_gently(int *nongit_ok)
12531295
dir.buf);
12541296
*nongit_ok = 1;
12551297
break;
1298+
case GIT_DIR_INVALID_OWNERSHIP:
1299+
if (!nongit_ok) {
1300+
struct strbuf quoted = STRBUF_INIT;
1301+
1302+
sq_quote_buf_pretty(&quoted, dir.buf);
1303+
die(_("unsafe repository ('%s' is owned by someone else)\n"
1304+
"To add an exception for this directory, call:\n"
1305+
"\n"
1306+
"\tgit config --global --add safe.directory %s"),
1307+
dir.buf, quoted.buf);
1308+
}
1309+
*nongit_ok = 1;
1310+
break;
12561311
case GIT_DIR_NONE:
12571312
/*
12581313
* As a safeguard against setup_git_directory_gently_1 returning

t/t0060-path-utils.sh

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,15 @@ fi
5555
ancestor() {
5656
# We do some math with the expected ancestor length.
5757
expected=$3
58-
if test -n "$rootoff" && test "x$expected" != x-1; then
59-
expected=$(($expected-$rootslash))
60-
test $expected -lt 0 ||
61-
expected=$(($expected+$rootoff))
62-
fi
63-
test_expect_success "longest ancestor: $1 $2 => $expected" \
58+
case "$rootoff,$expected,$2" in
59+
*,*,//*) ;; # leave UNC paths alone
60+
[0-9]*,[0-9]*,/*)
61+
# On Windows, expect MSYS2 pseudo root translation for
62+
# Unix-style absolute paths
63+
expected=$(($expected-$rootslash+$rootoff))
64+
;;
65+
esac
66+
test_expect_success $4 "longest ancestor: $1 $2 => $expected" \
6467
"actual=\$(test-tool path-utils longest_ancestor_length '$1' '$2') &&
6568
test \"\$actual\" = '$expected'"
6669
}
@@ -156,6 +159,11 @@ ancestor /foo/bar /foo 4
156159
ancestor /foo/bar /foo:/bar 4
157160
ancestor /foo/bar /bar -1
158161

162+
# Windows-specific: DOS drives, network shares
163+
ancestor C:/Users/me C:/ 2 MINGW
164+
ancestor D:/Users/me C:/ -1 MINGW
165+
ancestor //server/share/my-directory //server/share/ 14 MINGW
166+
159167
test_expect_success 'strip_path_suffix' '
160168
test c:/msysgit = $(test-tool path-utils strip_path_suffix \
161169
c:/msysgit/libexec//git-core libexec/git-core)

0 commit comments

Comments
 (0)