Skip to content

Commit 4ae8704

Browse files
authored
Merge pull request libgit2#6349 from libgit2/ethomson/cve-2022-29187
Fixes for CVE 2022-29187
2 parents d1001fd + ed24b8b commit 4ae8704

File tree

3 files changed

+122
-46
lines changed

3 files changed

+122
-46
lines changed

src/libgit2/repository.c

Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -488,60 +488,110 @@ static int read_gitfile(git_str *path_out, const char *file_path)
488488
typedef struct {
489489
const char *repo_path;
490490
git_str tmp;
491-
bool is_safe;
491+
bool *is_safe;
492492
} validate_ownership_data;
493493

494494
static int validate_ownership_cb(const git_config_entry *entry, void *payload)
495495
{
496496
validate_ownership_data *data = payload;
497497

498498
if (strcmp(entry->value, "") == 0)
499-
data->is_safe = false;
499+
*data->is_safe = false;
500500

501501
if (git_fs_path_prettify_dir(&data->tmp, entry->value, NULL) == 0 &&
502502
strcmp(data->tmp.ptr, data->repo_path) == 0)
503-
data->is_safe = true;
503+
*data->is_safe = true;
504504

505505
return 0;
506506
}
507507

508-
static int validate_ownership(const char *repo_path)
508+
static int validate_ownership_config(bool *is_safe, const char *path)
509509
{
510-
git_config *config = NULL;
511-
validate_ownership_data data = { repo_path, GIT_STR_INIT, false };
512-
git_fs_path_owner_t owner_level =
513-
GIT_FS_PATH_OWNER_CURRENT_USER |
514-
GIT_FS_PATH_USER_IS_ADMINISTRATOR;
515-
bool is_safe;
510+
validate_ownership_data ownership_data = {
511+
path, GIT_STR_INIT, is_safe
512+
};
513+
git_config *config;
516514
int error;
517515

518-
if ((error = git_fs_path_owner_is(&is_safe, repo_path, owner_level)) < 0) {
519-
if (error == GIT_ENOTFOUND)
520-
error = 0;
516+
if (load_global_config(&config) != 0)
517+
return 0;
521518

522-
goto done;
523-
}
519+
error = git_config_get_multivar_foreach(config,
520+
"safe.directory", NULL,
521+
validate_ownership_cb,
522+
&ownership_data);
523+
524+
git_config_free(config);
525+
git_str_dispose(&ownership_data.tmp);
524526

525-
if (is_safe) {
527+
return error;
528+
}
529+
530+
static int validate_ownership_path(bool *is_safe, const char *path)
531+
{
532+
git_fs_path_owner_t owner_level =
533+
GIT_FS_PATH_OWNER_CURRENT_USER |
534+
GIT_FS_PATH_USER_IS_ADMINISTRATOR |
535+
GIT_FS_PATH_OWNER_RUNNING_SUDO;
536+
int error = 0;
537+
538+
if (path)
539+
error = git_fs_path_owner_is(is_safe, path, owner_level);
540+
541+
if (error == GIT_ENOTFOUND) {
542+
*is_safe = true;
526543
error = 0;
527-
goto done;
528544
}
529545

530-
if (load_global_config(&config) == 0) {
531-
error = git_config_get_multivar_foreach(config, "safe.directory", NULL, validate_ownership_cb, &data);
546+
return error;
547+
}
548+
549+
static int validate_ownership(git_repository *repo)
550+
{
551+
const char *validation_paths[3] = { NULL }, *path;
552+
size_t validation_len = 0, i;
553+
bool is_safe = false;
554+
int error = 0;
555+
556+
/*
557+
* If there's a worktree, validate the permissions to it *and*
558+
* the git directory, and use the worktree as the configuration
559+
* key for allowlisting the directory. In a bare setup, only
560+
* look at the gitdir and use that as the allowlist. So we
561+
* examine all `validation_paths` but use only the first as
562+
* the configuration lookup.
563+
*/
564+
565+
if (repo->workdir)
566+
validation_paths[validation_len++] = repo->workdir;
532567

533-
if (!error && data.is_safe)
568+
if (repo->gitlink)
569+
validation_paths[validation_len++] = repo->gitlink;
570+
571+
validation_paths[validation_len++] = repo->gitdir;
572+
573+
for (i = 0; i < validation_len; i++) {
574+
path = validation_paths[i];
575+
576+
if ((error = validate_ownership_path(&is_safe, path)) < 0)
534577
goto done;
578+
579+
if (!is_safe)
580+
break;
535581
}
536582

537-
git_error_set(GIT_ERROR_CONFIG,
538-
"repository path '%s' is not owned by current user",
539-
repo_path);
540-
error = GIT_EOWNER;
583+
if (is_safe ||
584+
(error = validate_ownership_config(&is_safe, validation_paths[0])) < 0)
585+
goto done;
586+
587+
if (!is_safe) {
588+
git_error_set(GIT_ERROR_CONFIG,
589+
"repository path '%s' is not owned by current user",
590+
path);
591+
error = GIT_EOWNER;
592+
}
541593

542594
done:
543-
git_config_free(config);
544-
git_str_dispose(&data.tmp);
545595
return error;
546596
}
547597

@@ -918,7 +968,6 @@ int git_repository_open_ext(
918968
gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT;
919969
git_repository *repo = NULL;
920970
git_config *config = NULL;
921-
const char *validation_path;
922971
int version = 0;
923972

924973
if (flags & GIT_REPOSITORY_OPEN_FROM_ENV)
@@ -977,12 +1026,11 @@ int git_repository_open_ext(
9771026
}
9781027

9791028
/*
980-
* Ensure that the git directory is owned by the current user.
1029+
* Ensure that the git directory and worktree are
1030+
* owned by the current user.
9811031
*/
982-
validation_path = repo->is_bare ? repo->gitdir : repo->workdir;
983-
9841032
if (git_repository__validate_ownership &&
985-
(error = validate_ownership(validation_path)) < 0)
1033+
(error = validate_ownership(repo)) < 0)
9861034
goto cleanup;
9871035

9881036
cleanup:

src/util/fs_path.c

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,27 +1934,36 @@ int git_fs_path_owner_is(
19341934

19351935
#else
19361936

1937+
static int sudo_uid_lookup(uid_t *out)
1938+
{
1939+
git_str uid_str = GIT_STR_INIT;
1940+
int64_t uid;
1941+
int error;
1942+
1943+
if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 &&
1944+
(error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 &&
1945+
uid == (int64_t)((uid_t)uid)) {
1946+
*out = (uid_t)uid;
1947+
}
1948+
1949+
git_str_dispose(&uid_str);
1950+
return error;
1951+
}
1952+
19371953
int git_fs_path_owner_is(
19381954
bool *out,
19391955
const char *path,
19401956
git_fs_path_owner_t owner_type)
19411957
{
1942-
uid_t uids[2] = { 0 };
1943-
size_t uid_count = 0, i;
19441958
struct stat st;
1959+
uid_t euid, sudo_uid;
19451960

19461961
if (mock_owner) {
19471962
*out = ((mock_owner & owner_type) != 0);
19481963
return 0;
19491964
}
19501965

1951-
if (owner_type & GIT_FS_PATH_OWNER_CURRENT_USER)
1952-
uids[uid_count++] = geteuid();
1953-
1954-
if (owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR)
1955-
uids[uid_count++] = 0;
1956-
1957-
*out = false;
1966+
euid = geteuid();
19581967

19591968
if (p_lstat(path, &st) != 0) {
19601969
if (errno == ENOENT)
@@ -1964,13 +1973,27 @@ int git_fs_path_owner_is(
19641973
return -1;
19651974
}
19661975

1967-
for (i = 0; i < uid_count; i++) {
1968-
if (uids[i] == st.st_uid) {
1969-
*out = true;
1970-
break;
1971-
}
1976+
if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0 &&
1977+
st.st_uid == euid) {
1978+
*out = true;
1979+
return 0;
1980+
}
1981+
1982+
if ((owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0 &&
1983+
st.st_uid == 0) {
1984+
*out = true;
1985+
return 0;
1986+
}
1987+
1988+
if ((owner_type & GIT_FS_PATH_OWNER_RUNNING_SUDO) != 0 &&
1989+
euid == 0 &&
1990+
sudo_uid_lookup(&sudo_uid) == 0 &&
1991+
st.st_uid == sudo_uid) {
1992+
*out = true;
1993+
return 0;
19721994
}
19731995

1996+
*out = false;
19741997
return 0;
19751998
}
19761999

src/util/fs_path.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,13 @@ typedef enum {
747747
*/
748748
GIT_FS_PATH_USER_IS_ADMINISTRATOR = (1 << 2),
749749

750+
/**
751+
* The file is owned by the current user, who is running `sudo`.
752+
*/
753+
GIT_FS_PATH_OWNER_RUNNING_SUDO = (1 << 3),
754+
750755
/** The file may be owned by another user. */
751-
GIT_FS_PATH_OWNER_OTHER = (1 << 3)
756+
GIT_FS_PATH_OWNER_OTHER = (1 << 4)
752757
} git_fs_path_owner_t;
753758

754759
/**

0 commit comments

Comments
 (0)