Skip to content

Commit 2f8809f

Browse files
committed
Sync with 2.30.5
* maint-2.30: Git 2.30.5 setup: tighten ownership checks post CVE-2022-24765 git-compat-util: allow root to access both SUDO_UID and root owned t0034: add negative tests and allow git init to mostly work under sudo git-compat-util: avoid failing dir ownership checks if running privileged t: regression git needs safe.directory when using sudo
2 parents 09f66d6 + 88b7be6 commit 2f8809f

File tree

6 files changed

+251
-12
lines changed

6 files changed

+251
-12
lines changed

Documentation/RelNotes/2.30.5.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Git v2.30.5 Release Notes
2+
=========================
3+
4+
This release contains minor fix-ups for the changes that went into
5+
Git 2.30.3 and 2.30.4, addressing CVE-2022-29187.
6+
7+
* The safety check that verifies a safe ownership of the Git
8+
worktree is now extended to also cover the ownership of the Git
9+
directory (and the `.git` file, if there is any).
10+
11+
Carlo Marcelo Arenas Belón (1):
12+
setup: tighten ownership checks post CVE-2022-24765

Documentation/config/safe.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,17 @@ directory was listed in the `safe.directory` list. If `safe.directory=*`
2626
is set in system config and you want to re-enable this protection, then
2727
initialize your list with an empty value before listing the repositories
2828
that you deem safe.
29+
+
30+
As explained, Git only allows you to access repositories owned by
31+
yourself, i.e. the user who is running Git, by default. When Git
32+
is running as 'root' in a non Windows platform that provides sudo,
33+
however, git checks the SUDO_UID environment variable that sudo creates
34+
and will allow access to the uid recorded as its value in addition to
35+
the id from 'root'.
36+
This is to make it easy to perform a common sequence during installation
37+
"make && sudo make install". A git process running under 'sudo' runs as
38+
'root' but the 'sudo' command exports the environment variable to record
39+
which id the original user has.
40+
If that is not what you would prefer and want git to only trust
41+
repositories that are owned by root instead, then you can remove
42+
the `SUDO_UID` variable from root's environment before invoking git.

git-compat-util.h

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,68 @@ static inline int git_offset_1st_component(const char *path)
393393
#endif
394394

395395
#ifndef is_path_owned_by_current_user
396+
397+
#ifdef __TANDEM
398+
#define ROOT_UID 65535
399+
#else
400+
#define ROOT_UID 0
401+
#endif
402+
403+
/*
404+
* Do not use this function when
405+
* (1) geteuid() did not say we are running as 'root', or
406+
* (2) using this function will compromise the system.
407+
*
408+
* PORTABILITY WARNING:
409+
* This code assumes uid_t is unsigned because that is what sudo does.
410+
* If your uid_t type is signed and all your ids are positive then it
411+
* should all work fine.
412+
* If your version of sudo uses negative values for uid_t or it is
413+
* buggy and return an overflowed value in SUDO_UID, then git might
414+
* fail to grant access to your repository properly or even mistakenly
415+
* grant access to someone else.
416+
* In the unlikely scenario this happened to you, and that is how you
417+
* got to this message, we would like to know about it; so sent us an
418+
* email to [email protected] indicating which platform you are
419+
* using and which version of sudo, so we can improve this logic and
420+
* maybe provide you with a patch that would prevent this issue again
421+
* in the future.
422+
*/
423+
static inline void extract_id_from_env(const char *env, uid_t *id)
424+
{
425+
const char *real_uid = getenv(env);
426+
427+
/* discard anything empty to avoid a more complex check below */
428+
if (real_uid && *real_uid) {
429+
char *endptr = NULL;
430+
unsigned long env_id;
431+
432+
errno = 0;
433+
/* silent overflow errors could trigger a bug here */
434+
env_id = strtoul(real_uid, &endptr, 10);
435+
if (!*endptr && !errno)
436+
*id = env_id;
437+
}
438+
}
439+
396440
static inline int is_path_owned_by_current_uid(const char *path)
397441
{
398442
struct stat st;
443+
uid_t euid;
444+
399445
if (lstat(path, &st))
400446
return 0;
401-
return st.st_uid == geteuid();
447+
448+
euid = geteuid();
449+
if (euid == ROOT_UID)
450+
{
451+
if (st.st_uid == ROOT_UID)
452+
return 1;
453+
else
454+
extract_id_from_env("SUDO_UID", &euid);
455+
}
456+
457+
return st.st_uid == euid;
402458
}
403459

404460
#define is_path_owned_by_current_user is_path_owned_by_current_uid

setup.c

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,14 +1054,32 @@ static int safe_directory_cb(const char *key, const char *value, void *d)
10541054
return 0;
10551055
}
10561056

1057-
static int ensure_valid_ownership(const char *path)
1057+
/*
1058+
* Check if a repository is safe, by verifying the ownership of the
1059+
* worktree (if any), the git directory, and the gitfile (if any).
1060+
*
1061+
* Exemptions for known-safe repositories can be added via `safe.directory`
1062+
* config settings; for non-bare repositories, their worktree needs to be
1063+
* added, for bare ones their git directory.
1064+
*/
1065+
static int ensure_valid_ownership(const char *gitfile,
1066+
const char *worktree, const char *gitdir)
10581067
{
1059-
struct safe_directory_data data = { .path = path };
1068+
struct safe_directory_data data = {
1069+
.path = worktree ? worktree : gitdir
1070+
};
10601071

10611072
if (!git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0) &&
1062-
is_path_owned_by_current_user(path))
1073+
(!gitfile || is_path_owned_by_current_user(gitfile)) &&
1074+
(!worktree || is_path_owned_by_current_user(worktree)) &&
1075+
(!gitdir || is_path_owned_by_current_user(gitdir)))
10631076
return 1;
10641077

1078+
/*
1079+
* data.path is the "path" that identifies the repository and it is
1080+
* constant regardless of what failed above. data.is_safe should be
1081+
* initialized to false, and might be changed by the callback.
1082+
*/
10651083
read_very_early_config(safe_directory_cb, &data);
10661084

10671085
return data.is_safe;
@@ -1149,6 +1167,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
11491167
current_device = get_device_or_die(dir->buf, NULL, 0);
11501168
for (;;) {
11511169
int offset = dir->len, error_code = 0;
1170+
char *gitdir_path = NULL;
1171+
char *gitfile = NULL;
11521172

11531173
if (offset > min_offset)
11541174
strbuf_addch(dir, '/');
@@ -1159,21 +1179,50 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
11591179
if (die_on_error ||
11601180
error_code == READ_GITFILE_ERR_NOT_A_FILE) {
11611181
/* NEEDSWORK: fail if .git is not file nor dir */
1162-
if (is_git_directory(dir->buf))
1182+
if (is_git_directory(dir->buf)) {
11631183
gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
1184+
gitdir_path = xstrdup(dir->buf);
1185+
}
11641186
} else if (error_code != READ_GITFILE_ERR_STAT_FAILED)
11651187
return GIT_DIR_INVALID_GITFILE;
1166-
}
1188+
} else
1189+
gitfile = xstrdup(dir->buf);
1190+
/*
1191+
* Earlier, we tentatively added DEFAULT_GIT_DIR_ENVIRONMENT
1192+
* to check that directory for a repository.
1193+
* Now trim that tentative addition away, because we want to
1194+
* focus on the real directory we are in.
1195+
*/
11671196
strbuf_setlen(dir, offset);
11681197
if (gitdirenv) {
1169-
if (!ensure_valid_ownership(dir->buf))
1170-
return GIT_DIR_INVALID_OWNERSHIP;
1171-
strbuf_addstr(gitdir, gitdirenv);
1172-
return GIT_DIR_DISCOVERED;
1198+
enum discovery_result ret;
1199+
1200+
if (ensure_valid_ownership(gitfile,
1201+
dir->buf,
1202+
(gitdir_path ? gitdir_path : gitdirenv))) {
1203+
strbuf_addstr(gitdir, gitdirenv);
1204+
ret = GIT_DIR_DISCOVERED;
1205+
} else
1206+
ret = GIT_DIR_INVALID_OWNERSHIP;
1207+
1208+
/*
1209+
* Earlier, during discovery, we might have allocated
1210+
* string copies for gitdir_path or gitfile so make
1211+
* sure we don't leak by freeing them now, before
1212+
* leaving the loop and function.
1213+
*
1214+
* Note: gitdirenv will be non-NULL whenever these are
1215+
* allocated, therefore we need not take care of releasing
1216+
* them outside of this conditional block.
1217+
*/
1218+
free(gitdir_path);
1219+
free(gitfile);
1220+
1221+
return ret;
11731222
}
11741223

11751224
if (is_git_directory(dir->buf)) {
1176-
if (!ensure_valid_ownership(dir->buf))
1225+
if (!ensure_valid_ownership(NULL, NULL, dir->buf))
11771226
return GIT_DIR_INVALID_OWNERSHIP;
11781227
strbuf_addstr(gitdir, ".");
11791228
return GIT_DIR_BARE;
@@ -1306,7 +1355,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
13061355
struct strbuf quoted = STRBUF_INIT;
13071356

13081357
sq_quote_buf_pretty(&quoted, dir.buf);
1309-
die(_("unsafe repository ('%s' is owned by someone else)\n"
1358+
die(_("detected dubious ownership in repository at '%s'\n"
13101359
"To add an exception for this directory, call:\n"
13111360
"\n"
13121361
"\tgit config --global --add safe.directory %s"),

t/lib-sudo.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Helpers for running git commands under sudo.
2+
3+
# Runs a scriplet passed through stdin under sudo.
4+
run_with_sudo () {
5+
local ret
6+
local RUN="$TEST_DIRECTORY/$$.sh"
7+
write_script "$RUN" "$TEST_SHELL_PATH"
8+
# avoid calling "$RUN" directly so sudo doesn't get a chance to
9+
# override the shell, add aditional restrictions or even reject
10+
# running the script because its security policy deem it unsafe
11+
sudo "$TEST_SHELL_PATH" -c "\"$RUN\""
12+
ret=$?
13+
rm -f "$RUN"
14+
return $ret
15+
}

t/t0034-root-safe-directory.sh

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/bin/sh
2+
3+
test_description='verify safe.directory checks while running as root'
4+
5+
. ./test-lib.sh
6+
. "$TEST_DIRECTORY"/lib-sudo.sh
7+
8+
if [ "$GIT_TEST_ALLOW_SUDO" != "YES" ]
9+
then
10+
skip_all="You must set env var GIT_TEST_ALLOW_SUDO=YES in order to run this test"
11+
test_done
12+
fi
13+
14+
if ! test_have_prereq NOT_ROOT
15+
then
16+
skip_all="These tests do not support running as root"
17+
test_done
18+
fi
19+
20+
test_lazy_prereq SUDO '
21+
sudo -n id -u >u &&
22+
id -u root >r &&
23+
test_cmp u r &&
24+
command -v git >u &&
25+
sudo command -v git >r &&
26+
test_cmp u r
27+
'
28+
29+
if ! test_have_prereq SUDO
30+
then
31+
skip_all="Your sudo/system configuration is either too strict or unsupported"
32+
test_done
33+
fi
34+
35+
test_expect_success SUDO 'setup' '
36+
sudo rm -rf root &&
37+
mkdir -p root/r &&
38+
(
39+
cd root/r &&
40+
git init
41+
)
42+
'
43+
44+
test_expect_success SUDO 'sudo git status as original owner' '
45+
(
46+
cd root/r &&
47+
git status &&
48+
sudo git status
49+
)
50+
'
51+
52+
test_expect_success SUDO 'setup root owned repository' '
53+
sudo mkdir -p root/p &&
54+
sudo git init root/p
55+
'
56+
57+
test_expect_success 'cannot access if owned by root' '
58+
(
59+
cd root/p &&
60+
test_must_fail git status
61+
)
62+
'
63+
64+
test_expect_success 'can access if addressed explicitly' '
65+
(
66+
cd root/p &&
67+
GIT_DIR=.git GIT_WORK_TREE=. git status
68+
)
69+
'
70+
71+
test_expect_success SUDO 'can access with sudo if root' '
72+
(
73+
cd root/p &&
74+
sudo git status
75+
)
76+
'
77+
78+
test_expect_success SUDO 'can access with sudo if root by removing SUDO_UID' '
79+
(
80+
cd root/p &&
81+
run_with_sudo <<-END
82+
unset SUDO_UID &&
83+
git status
84+
END
85+
)
86+
'
87+
88+
# this MUST be always the last test
89+
test_expect_success SUDO 'cleanup' '
90+
sudo rm -rf root
91+
'
92+
93+
test_done

0 commit comments

Comments
 (0)