Unverified Commit ca55adaa by Edward Thomson Committed by GitHub

Merge pull request #4666 from pks-t/pks/v0.26.4

Release v0.26.4
parents b55bb43d 9fcd4772
v0.26.4
-------
This is a security release fixing insufficient validation of submodule names
(CVE-2018-11235, reported by Etienne Stalmans) and disallows `.gitmodules` files
as symlinks.
While submodule names come from the untrusted ".gitmodules" file, we blindly
append the name to "$GIT_DIR/modules" to construct the final path of the
submodule repository. In case the name contains e.g. "../", an adversary would
be able to escape your repository and write data at arbitrary paths. In
accordance with git, we now enforce some rules for submodule names which will
cause libgit2 to ignore these malicious names.
Adding a symlink as `.gitmodules` into the index from the workdir or checking
out such files is not allowed as this can make a Git implementation write
outside of the repository and bypass the `fsck` checks for CVE-2018-11235.
libgit2 is not susceptible to CVE-2018-11233.
v0.26.3 v0.26.3
------- -------
......
...@@ -7,10 +7,10 @@ ...@@ -7,10 +7,10 @@
#ifndef INCLUDE_git_version_h__ #ifndef INCLUDE_git_version_h__
#define INCLUDE_git_version_h__ #define INCLUDE_git_version_h__
#define LIBGIT2_VERSION "0.26.3" #define LIBGIT2_VERSION "0.26.4"
#define LIBGIT2_VER_MAJOR 0 #define LIBGIT2_VER_MAJOR 0
#define LIBGIT2_VER_MINOR 26 #define LIBGIT2_VER_MINOR 26
#define LIBGIT2_VER_REVISION 3 #define LIBGIT2_VER_REVISION 4
#define LIBGIT2_VER_PATCH 0 #define LIBGIT2_VER_PATCH 0
#define LIBGIT2_SOVERSION 26 #define LIBGIT2_SOVERSION 26
......
...@@ -1272,14 +1272,14 @@ static int checkout_verify_paths( ...@@ -1272,14 +1272,14 @@ static int checkout_verify_paths(
unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS; unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS;
if (action & CHECKOUT_ACTION__REMOVE) { if (action & CHECKOUT_ACTION__REMOVE) {
if (!git_path_isvalid(repo, delta->old_file.path, flags)) { if (!git_path_isvalid(repo, delta->old_file.path, delta->old_file.mode, flags)) {
giterr_set(GITERR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path); giterr_set(GITERR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path);
return -1; return -1;
} }
} }
if (action & ~CHECKOUT_ACTION__REMOVE) { if (action & ~CHECKOUT_ACTION__REMOVE) {
if (!git_path_isvalid(repo, delta->new_file.path, flags)) { if (!git_path_isvalid(repo, delta->new_file.path, delta->new_file.mode, flags)) {
giterr_set(GITERR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path); giterr_set(GITERR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path);
return -1; return -1;
} }
......
...@@ -884,11 +884,13 @@ static int index_entry_create( ...@@ -884,11 +884,13 @@ static int index_entry_create(
git_index_entry **out, git_index_entry **out,
git_repository *repo, git_repository *repo,
const char *path, const char *path,
struct stat *st,
bool from_workdir) bool from_workdir)
{ {
size_t pathlen = strlen(path), alloclen; size_t pathlen = strlen(path), alloclen;
struct entry_internal *entry; struct entry_internal *entry;
unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS; unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS;
uint16_t mode = 0;
/* always reject placing `.git` in the index and directory traversal. /* always reject placing `.git` in the index and directory traversal.
* when requested, disallow platform-specific filenames and upgrade to * when requested, disallow platform-specific filenames and upgrade to
...@@ -896,8 +898,10 @@ static int index_entry_create( ...@@ -896,8 +898,10 @@ static int index_entry_create(
*/ */
if (from_workdir) if (from_workdir)
path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS; path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS;
if (st)
mode = st->st_mode;
if (!git_path_isvalid(repo, path, path_valid_flags)) { if (!git_path_isvalid(repo, path, mode, path_valid_flags)) {
giterr_set(GITERR_INDEX, "invalid path: '%s'", path); giterr_set(GITERR_INDEX, "invalid path: '%s'", path);
return -1; return -1;
} }
...@@ -922,15 +926,35 @@ static int index_entry_init( ...@@ -922,15 +926,35 @@ static int index_entry_init(
{ {
int error = 0; int error = 0;
git_index_entry *entry = NULL; git_index_entry *entry = NULL;
git_buf path = GIT_BUF_INIT;
struct stat st; struct stat st;
git_oid oid; git_oid oid;
git_repository *repo;
if (INDEX_OWNER(index) == NULL) if (INDEX_OWNER(index) == NULL)
return create_index_error(-1, return create_index_error(-1,
"could not initialize index entry. " "could not initialize index entry. "
"Index is not backed up by an existing repository."); "Index is not backed up by an existing repository.");
if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, true) < 0) /*
* FIXME: this is duplicated with the work in
* git_blob__create_from_paths. It should accept an optional stat
* structure so we can pass in the one we have to do here.
*/
repo = INDEX_OWNER(index);
if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
return GIT_EBAREREPO;
if (git_buf_joinpath(&path, git_repository_workdir(repo), rel_path) < 0)
return -1;
error = git_path_lstat(path.ptr, &st);
git_buf_free(&path);
if (error < 0)
return error;
if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, &st, true) < 0)
return -1; return -1;
/* write the blob to disk and get the oid and stat info */ /* write the blob to disk and get the oid and stat info */
...@@ -1016,7 +1040,7 @@ static int index_entry_dup( ...@@ -1016,7 +1040,7 @@ static int index_entry_dup(
git_index *index, git_index *index,
const git_index_entry *src) const git_index_entry *src)
{ {
if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0) if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0)
return -1; return -1;
index_entry_cpy(*out, src); index_entry_cpy(*out, src);
...@@ -1038,7 +1062,7 @@ static int index_entry_dup_nocache( ...@@ -1038,7 +1062,7 @@ static int index_entry_dup_nocache(
git_index *index, git_index *index,
const git_index_entry *src) const git_index_entry *src)
{ {
if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0) if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0)
return -1; return -1;
index_entry_cpy_nocache(*out, src); index_entry_cpy_nocache(*out, src);
...@@ -1457,9 +1481,6 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const ...@@ -1457,9 +1481,6 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const
struct stat st; struct stat st;
int error; int error;
if (index_entry_create(&entry, INDEX_OWNER(index), path, true) < 0)
return -1;
if ((error = git_buf_joinpath(&abspath, git_repository_workdir(repo), path)) < 0) if ((error = git_buf_joinpath(&abspath, git_repository_workdir(repo), path)) < 0)
return error; return error;
...@@ -1468,6 +1489,9 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const ...@@ -1468,6 +1489,9 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const
return -1; return -1;
} }
if (index_entry_create(&entry, INDEX_OWNER(index), path, &st, true) < 0)
return -1;
git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode);
if ((error = git_repository_open(&sub, abspath.ptr)) < 0) if ((error = git_repository_open(&sub, abspath.ptr)) < 0)
...@@ -2961,7 +2985,7 @@ static int read_tree_cb( ...@@ -2961,7 +2985,7 @@ static int read_tree_cb(
if (git_buf_joinpath(&path, root, tentry->filename) < 0) if (git_buf_joinpath(&path, root, tentry->filename) < 0)
return -1; return -1;
if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, false) < 0) if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, NULL, false) < 0)
return -1; return -1;
entry->mode = tentry->attr; entry->mode = tentry->attr;
......
...@@ -1560,18 +1560,31 @@ static int32_t next_hfs_char(const char **in, size_t *len) ...@@ -1560,18 +1560,31 @@ static int32_t next_hfs_char(const char **in, size_t *len)
return 0; /* NULL byte -- end of string */ return 0; /* NULL byte -- end of string */
} }
static bool verify_dotgit_hfs(const char *path, size_t len) static bool verify_dotgit_hfs_generic(const char *path, size_t len, const char *needle, size_t needle_len)
{ {
if (next_hfs_char(&path, &len) != '.' || size_t i;
next_hfs_char(&path, &len) != 'g' || char c;
next_hfs_char(&path, &len) != 'i' ||
next_hfs_char(&path, &len) != 't' || if (next_hfs_char(&path, &len) != '.')
next_hfs_char(&path, &len) != 0) return true;
for (i = 0; i < needle_len; i++) {
c = next_hfs_char(&path, &len);
if (c != needle[i])
return true;
}
if (next_hfs_char(&path, &len) != '\0')
return true; return true;
return false; return false;
} }
static bool verify_dotgit_hfs(const char *path, size_t len)
{
return verify_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git"));
}
GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len) GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len)
{ {
git_buf *reserved = git_repository__reserved_names_win32; git_buf *reserved = git_repository__reserved_names_win32;
...@@ -1607,6 +1620,57 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size ...@@ -1607,6 +1620,57 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
return false; return false;
} }
GIT_INLINE(bool) only_spaces_and_dots(const char *path)
{
const char *c = path;
for (;; c++) {
if (*c == '\0')
return true;
if (*c != ' ' && *c != '.')
return false;
}
return true;
}
GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const char *dotgit_name, size_t dotgit_len, const char *shortname_pfix)
{
int i, saw_tilde;
if (name[0] == '.' && len >= dotgit_len &&
!strncasecmp(name + 1, dotgit_name, dotgit_len)) {
return !only_spaces_and_dots(name + dotgit_len + 1);
}
/* Detect the basic NTFS shortname with the first six chars */
if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
name[7] >= '1' && name[7] <= '4')
return !only_spaces_and_dots(name + 8);
/* Catch fallback names */
for (i = 0, saw_tilde = 0; i < 8; i++) {
if (name[i] == '\0') {
return true;
} else if (saw_tilde) {
if (name[i] < '0' || name[i] > '9')
return true;
} else if (name[i] == '~') {
if (name[i+1] < '1' || name[i+1] > '9')
return true;
saw_tilde = 1;
} else if (i >= 6) {
return true;
} else if (name[i] < 0) {
return true;
} else if (git__tolower(name[i]) != shortname_pfix[i]) {
return true;
}
}
return !only_spaces_and_dots(name + i);
}
GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
{ {
if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\') if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\')
...@@ -1635,6 +1699,24 @@ GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) ...@@ -1635,6 +1699,24 @@ GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
} }
/* /*
* Return the length of the common prefix between str and prefix, comparing them
* case-insensitively (must be ASCII to match).
*/
GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix)
{
size_t count = 0;
while (len >0 && tolower(*str) == tolower(*prefix)) {
count++;
str++;
prefix++;
len--;
}
return count;
}
/*
* We fundamentally don't like some paths when dealing with user-inputted * We fundamentally don't like some paths when dealing with user-inputted
* strings (in checkout or ref names): we don't want dot or dot-dot * strings (in checkout or ref names): we don't want dot or dot-dot
* anywhere, we want to avoid writing weird paths on Windows that can't * anywhere, we want to avoid writing weird paths on Windows that can't
...@@ -1647,6 +1729,7 @@ static bool verify_component( ...@@ -1647,6 +1729,7 @@ static bool verify_component(
git_repository *repo, git_repository *repo,
const char *component, const char *component,
size_t len, size_t len,
uint16_t mode,
unsigned int flags) unsigned int flags)
{ {
if (len == 0) if (len == 0)
...@@ -1679,26 +1762,38 @@ static bool verify_component( ...@@ -1679,26 +1762,38 @@ static bool verify_component(
return false; return false;
} }
if (flags & GIT_PATH_REJECT_DOT_GIT_HFS && if (flags & GIT_PATH_REJECT_DOT_GIT_HFS) {
!verify_dotgit_hfs(component, len)) if (!verify_dotgit_hfs(component, len))
return false; return false;
if (S_ISLNK(mode) && git_path_is_hfs_dotgit_modules(component, len))
return false;
}
if (flags & GIT_PATH_REJECT_DOT_GIT_NTFS && if (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) {
!verify_dotgit_ntfs(repo, component, len)) if (!verify_dotgit_ntfs(repo, component, len))
return false; return false;
if (S_ISLNK(mode) && git_path_is_ntfs_dotgit_modules(component, len))
return false;
}
/* don't bother rerunning the `.git` test if we ran the HFS or NTFS /* don't bother rerunning the `.git` test if we ran the HFS or NTFS
* specific tests, they would have already rejected `.git`. * specific tests, they would have already rejected `.git`.
*/ */
if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 &&
(flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 &&
(flags & GIT_PATH_REJECT_DOT_GIT_LITERAL) && (flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) {
len == 4 && if (len >= 4 &&
component[0] == '.' && component[0] == '.' &&
(component[1] == 'g' || component[1] == 'G') && (component[1] == 'g' || component[1] == 'G') &&
(component[2] == 'i' || component[2] == 'I') && (component[2] == 'i' || component[2] == 'I') &&
(component[3] == 't' || component[3] == 'T')) (component[3] == 't' || component[3] == 'T')) {
return false; if (len == 4)
return false;
if (S_ISLNK(mode) && common_prefix_icase(component, len, ".gitmodules") == len)
return false;
}
}
return true; return true;
} }
...@@ -1736,6 +1831,7 @@ GIT_INLINE(unsigned int) dotgit_flags( ...@@ -1736,6 +1831,7 @@ GIT_INLINE(unsigned int) dotgit_flags(
bool git_path_isvalid( bool git_path_isvalid(
git_repository *repo, git_repository *repo,
const char *path, const char *path,
uint16_t mode,
unsigned int flags) unsigned int flags)
{ {
const char *start, *c; const char *start, *c;
...@@ -1749,14 +1845,14 @@ bool git_path_isvalid( ...@@ -1749,14 +1845,14 @@ bool git_path_isvalid(
return false; return false;
if (*c == '/') { if (*c == '/') {
if (!verify_component(repo, start, (c - start), flags)) if (!verify_component(repo, start, (c - start), mode, flags))
return false; return false;
start = c+1; start = c+1;
} }
} }
return verify_component(repo, start, (c - start), flags); return verify_component(repo, start, (c - start), mode, flags);
} }
int git_path_normalize_slashes(git_buf *out, const char *path) int git_path_normalize_slashes(git_buf *out, const char *path)
...@@ -1774,3 +1870,65 @@ int git_path_normalize_slashes(git_buf *out, const char *path) ...@@ -1774,3 +1870,65 @@ int git_path_normalize_slashes(git_buf *out, const char *path)
return 0; return 0;
} }
static int verify_dotgit_generic(const char *name, size_t len, const char *dotgit_name, size_t dotgit_len, const char *shortname_pfix)
{
if (!verify_dotgit_ntfs_generic(name, len, dotgit_name, dotgit_len, shortname_pfix))
return false;
return verify_dotgit_hfs_generic(name, len, dotgit_name, dotgit_len);
}
int git_path_is_ntfs_dotgit_modules(const char *name, size_t len)
{
return !verify_dotgit_ntfs_generic(name, len, "gitmodules", CONST_STRLEN("gitmodules"), "gi7eba");
}
int git_path_is_hfs_dotgit_modules(const char *name, size_t len)
{
return !verify_dotgit_hfs_generic(name, len, "gitmodules", CONST_STRLEN("gitmodules"));
}
int git_path_is_dotgit_modules(const char *name, size_t len)
{
if (git_path_is_hfs_dotgit_modules(name, len))
return 1;
return git_path_is_ntfs_dotgit_modules(name, len);
}
int git_path_is_ntfs_dotgit_ignore(const char *name, size_t len)
{
return !verify_dotgit_ntfs_generic(name, len, "gitignore", CONST_STRLEN("gitignore"), "gi250a");
}
int git_path_is_hfs_dotgit_ignore(const char *name, size_t len)
{
return !verify_dotgit_hfs_generic(name, len, "gitignore", CONST_STRLEN("gitignore"));
}
int git_path_is_dotgit_ignore(const char *name, size_t len)
{
if (git_path_is_hfs_dotgit_ignore(name, len))
return 1;
return git_path_is_ntfs_dotgit_ignore(name, len);
}
int git_path_is_hfs_dotgit_attributes(const char *name, size_t len)
{
return !verify_dotgit_hfs_generic(name, len, "gitattributes", CONST_STRLEN("gitattributes"));
}
int git_path_is_ntfs_dotgit_attributes(const char *name, size_t len)
{
return !verify_dotgit_ntfs_generic(name, len, "gitattributes", CONST_STRLEN("gitattributes"), "gi7d29");
}
int git_path_is_dotgit_attributes(const char *name, size_t len)
{
if (git_path_is_hfs_dotgit_attributes(name, len))
return 1;
return git_path_is_ntfs_dotgit_attributes(name, len);
}
...@@ -623,6 +623,7 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or ...@@ -623,6 +623,7 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or
extern bool git_path_isvalid( extern bool git_path_isvalid(
git_repository *repo, git_repository *repo,
const char *path, const char *path,
uint16_t mode,
unsigned int flags); unsigned int flags);
/** /**
...@@ -630,4 +631,76 @@ extern bool git_path_isvalid( ...@@ -630,4 +631,76 @@ extern bool git_path_isvalid(
*/ */
int git_path_normalize_slashes(git_buf *out, const char *path); int git_path_normalize_slashes(git_buf *out, const char *path);
/**
* Check whether a path component corresponds to a .gitmodules file
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_dotgit_modules(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitmodules file in NTFS
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_ntfs_dotgit_modules(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitmodules file in HFS+
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_hfs_dotgit_modules(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitignore file
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_dotgit_ignore(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitignore file in NTFS
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_ntfs_dotgit_ignore(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitignore file in HFS+
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_hfs_dotgit_ignore(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitignore file
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_dotgit_attributes(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitattributes file in NTFS
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_ntfs_dotgit_attributes(const char *name, size_t len);
/**
* Check whether a path component corresponds to a .gitattributes file in HFS+
*
* @param name the path component to check
* @param len the length of `name`
*/
extern int git_path_is_hfs_dotgit_attributes(const char *name, size_t len);
#endif #endif
...@@ -743,7 +743,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * ...@@ -743,7 +743,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
assert(file && backend && name); assert(file && backend && name);
if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) { if (!git_path_isvalid(backend->repo, name, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
giterr_set(GITERR_INVALID, "invalid reference name '%s'", name); giterr_set(GITERR_INVALID, "invalid reference name '%s'", name);
return GIT_EINVALIDSPEC; return GIT_EINVALIDSPEC;
} }
...@@ -1739,7 +1739,7 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char ...@@ -1739,7 +1739,7 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
repo = backend->repo; repo = backend->repo;
if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) { if (!git_path_isvalid(backend->repo, refname, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
giterr_set(GITERR_INVALID, "invalid reference name '%s'", refname); giterr_set(GITERR_INVALID, "invalid reference name '%s'", refname);
return GIT_EINVALIDSPEC; return GIT_EINVALIDSPEC;
} }
......
...@@ -169,13 +169,13 @@ static void free_submodule_names(git_strmap *names) ...@@ -169,13 +169,13 @@ static void free_submodule_names(git_strmap *names)
* TODO: for some use-cases, this might need case-folding on a * TODO: for some use-cases, this might need case-folding on a
* case-insensitive filesystem * case-insensitive filesystem
*/ */
static int load_submodule_names(git_strmap *out, git_config *cfg) static int load_submodule_names(git_strmap *out, git_repository *repo, git_config *cfg)
{ {
const char *key = "submodule\\..*\\.path"; const char *key = "submodule\\..*\\.path";
git_config_iterator *iter; git_config_iterator *iter;
git_config_entry *entry; git_config_entry *entry;
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
int rval; int rval, isvalid;
int error = 0; int error = 0;
if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0)
...@@ -186,16 +186,29 @@ static int load_submodule_names(git_strmap *out, git_config *cfg) ...@@ -186,16 +186,29 @@ static int load_submodule_names(git_strmap *out, git_config *cfg)
fdot = strchr(entry->name, '.'); fdot = strchr(entry->name, '.');
ldot = strrchr(entry->name, '.'); ldot = strrchr(entry->name, '.');
git_buf_clear(&buf);
git_buf_put(&buf, fdot + 1, ldot - fdot - 1); git_buf_put(&buf, fdot + 1, ldot - fdot - 1);
isvalid = git_submodule_name_is_valid(repo, buf.ptr, 0);
if (isvalid < 0) {
error = isvalid;
goto out;
}
if (!isvalid)
continue;
git_strmap_insert(out, entry->value, git_buf_detach(&buf), &rval); git_strmap_insert(out, entry->value, git_buf_detach(&buf), &rval);
if (rval < 0) { if (rval < 0) {
giterr_set(GITERR_NOMEMORY, "error inserting submodule into hash table"); giterr_set(GITERR_NOMEMORY, "error inserting submodule into hash table");
return -1; return -1;
} }
} }
if (error == GIT_ITEROVER)
error = 0;
out:
git_buf_free(&buf);
git_config_iterator_free(iter); git_config_iterator_free(iter);
return 0; return error;
} }
int git_submodule_lookup( int git_submodule_lookup(
...@@ -309,6 +322,28 @@ int git_submodule_lookup( ...@@ -309,6 +322,28 @@ int git_submodule_lookup(
return 0; return 0;
} }
int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags)
{
git_buf buf = GIT_BUF_INIT;
int error, isvalid;
if (flags == 0)
flags = GIT_PATH_REJECT_FILESYSTEM_DEFAULTS;
/* Avoid allocating a new string if we can avoid it */
if (strchr(name, '\\') != NULL) {
if ((error = git_path_normalize_slashes(&buf, name)) < 0)
return error;
} else {
git_buf_attach_notowned(&buf, name, strlen(name));
}
isvalid = git_path_isvalid(repo, buf.ptr, 0, flags);
git_buf_free(&buf);
return isvalid;
}
static void submodule_free_dup(void *sm) static void submodule_free_dup(void *sm)
{ {
git_submodule_free(sm); git_submodule_free(sm);
...@@ -354,7 +389,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cf ...@@ -354,7 +389,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cf
git_strmap *names = 0; git_strmap *names = 0;
git_strmap_alloc(&names); git_strmap_alloc(&names);
if ((error = load_submodule_names(names, cfg))) if ((error = load_submodule_names(names, git_index_owner(idx), cfg)))
goto done; goto done;
if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0) if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0)
...@@ -406,7 +441,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg ...@@ -406,7 +441,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg
const git_index_entry *entry; const git_index_entry *entry;
git_strmap *names = 0; git_strmap *names = 0;
git_strmap_alloc(&names); git_strmap_alloc(&names);
if ((error = load_submodule_names(names, cfg))) if ((error = load_submodule_names(names, git_tree_owner(head), cfg)))
goto done; goto done;
if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) if ((error = git_iterator_for_tree(&i, head, NULL)) < 0)
...@@ -1492,13 +1527,19 @@ static int submodule_update_head(git_submodule *submodule) ...@@ -1492,13 +1527,19 @@ static int submodule_update_head(git_submodule *submodule)
int git_submodule_reload(git_submodule *sm, int force) int git_submodule_reload(git_submodule *sm, int force)
{ {
int error = 0; int error = 0, isvalid;
git_config *mods; git_config *mods;
GIT_UNUSED(force); GIT_UNUSED(force);
assert(sm); assert(sm);
isvalid = git_submodule_name_is_valid(sm->repo, sm->name, 0);
if (isvalid <= 0) {
/* This should come with a warning, but we've no API for that */
return isvalid;
}
if (!git_repository_is_bare(sm->repo)) { if (!git_repository_is_bare(sm->repo)) {
/* refresh config data */ /* refresh config data */
mods = gitmodules_snapshot(sm->repo); mods = gitmodules_snapshot(sm->repo);
...@@ -1839,7 +1880,7 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) ...@@ -1839,7 +1880,7 @@ static int submodule_load_each(const git_config_entry *entry, void *payload)
git_strmap *map = data->map; git_strmap *map = data->map;
git_buf name = GIT_BUF_INIT; git_buf name = GIT_BUF_INIT;
git_submodule *sm; git_submodule *sm;
int error; int error, isvalid;
if (git__prefixcmp(entry->name, "submodule.") != 0) if (git__prefixcmp(entry->name, "submodule.") != 0)
return 0; return 0;
...@@ -1855,6 +1896,12 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) ...@@ -1855,6 +1896,12 @@ static int submodule_load_each(const git_config_entry *entry, void *payload)
if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0) if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0)
return error; return error;
isvalid = git_submodule_name_is_valid(data->repo, name.ptr, 0);
if (isvalid <= 0) {
error = isvalid;
goto done;
}
/* /*
* Now that we have the submodule's name, we can use that to * Now that we have the submodule's name, we can use that to
* figure out whether it's in the map. If it's not, we create * figure out whether it's in the map. If it's not, we create
......
...@@ -146,4 +146,17 @@ extern int git_submodule_parse_update( ...@@ -146,4 +146,17 @@ extern int git_submodule_parse_update(
extern int git_submodule__map( extern int git_submodule__map(
git_repository *repo, git_repository *repo,
git_strmap *map); git_strmap *map);
/**
* Check whether a submodule's name is valid.
*
* Check the path against the path validity rules, either the filesystem
* defaults (like checkout does) or whichever you want to compare against.
*
* @param repo the repository which contains the submodule
* @param name the name to check
* @param flags the `GIT_PATH` flags to use for the check (0 to use filesystem defaults)
*/
extern int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags);
#endif #endif
...@@ -54,7 +54,7 @@ GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) ...@@ -54,7 +54,7 @@ GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
static int valid_entry_name(git_repository *repo, const char *filename) static int valid_entry_name(git_repository *repo, const char *filename)
{ {
return *filename != '\0' && return *filename != '\0' &&
git_path_isvalid(repo, filename, git_path_isvalid(repo, filename, 0,
GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_PATH_REJECT_SLASH); GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_PATH_REJECT_SLASH);
} }
......
...@@ -364,3 +364,15 @@ void test_checkout_nasty__symlink3(void) ...@@ -364,3 +364,15 @@ void test_checkout_nasty__symlink3(void)
test_checkout_passes("refs/heads/symlink3", ".git/foobar"); test_checkout_passes("refs/heads/symlink3", ".git/foobar");
} }
void test_checkout_nasty__gitmodules_symlink(void)
{
cl_repo_set_bool(repo, "core.protectHFS", true);
test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules");
cl_repo_set_bool(repo, "core.protectHFS", false);
cl_repo_set_bool(repo, "core.protectNTFS", true);
test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules");
cl_repo_set_bool(repo, "core.protectNTFS", false);
test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules");
}
...@@ -348,7 +348,7 @@ void test_online_clone__credentials(void) ...@@ -348,7 +348,7 @@ void test_online_clone__credentials(void)
void test_online_clone__bitbucket_style(void) void test_online_clone__bitbucket_style(void)
{ {
git_cred_userpass_payload user_pass = { git_cred_userpass_payload user_pass = {
"libgit2", "libgit2" "libgit3", "libgit3"
}; };
g_options.fetch_opts.callbacks.credentials = git_cred_userpass; g_options.fetch_opts.callbacks.credentials = git_cred_userpass;
...@@ -357,15 +357,45 @@ void test_online_clone__bitbucket_style(void) ...@@ -357,15 +357,45 @@ void test_online_clone__bitbucket_style(void)
cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options)); cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options));
git_repository_free(g_repo); g_repo = NULL; git_repository_free(g_repo); g_repo = NULL;
cl_fixture_cleanup("./foo"); cl_fixture_cleanup("./foo");
}
/* User and pass from URL */ void test_online_clone__bitbucket_uses_creds_in_url(void)
user_pass.password = "wrong"; {
git_cred_userpass_payload user_pass = {
"libgit2", "wrong"
};
g_options.fetch_opts.callbacks.credentials = git_cred_userpass;
g_options.fetch_opts.callbacks.payload = &user_pass;
/*
* Correct user and pass are in the URL; the (incorrect) creds in
* the `git_cred_userpass_payload` should be ignored.
*/
cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options)); cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options));
git_repository_free(g_repo); g_repo = NULL; git_repository_free(g_repo); g_repo = NULL;
cl_fixture_cleanup("./foo"); cl_fixture_cleanup("./foo");
}
/* Wrong password in URL, fall back to user_pass */ void test_online_clone__bitbucket_falls_back_to_specified_creds(void)
user_pass.password = "libgit2"; {
git_cred_userpass_payload user_pass = {
"libgit2", "libgit2"
};
g_options.fetch_opts.callbacks.credentials = git_cred_userpass;
g_options.fetch_opts.callbacks.payload = &user_pass;
/*
* TODO: as of March 2018, bitbucket sporadically fails with
* 403s instead of replying with a 401 - but only sometimes.
*/
cl_skip();
/*
* Incorrect user and pass are in the URL; the (correct) creds in
* the `git_cred_userpass_payload` should be used as a fallback.
*/
cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options)); cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options));
git_repository_free(g_repo); g_repo = NULL; git_repository_free(g_repo); g_repo = NULL;
cl_fixture_cleanup("./foo"); cl_fixture_cleanup("./foo");
......
#include "clar_libgit2.h"
#include "path.h"
static char *gitmodules_altnames[] = {
".gitmodules",
/*
* Equivalent to the ".git\u200cmodules" string from git but hard-coded
* as a UTF-8 sequence
*/
".git\xe2\x80\x8cmodules",
".Gitmodules",
".gitmoduleS",
".gitmodules ",
".gitmodules.",
".gitmodules ",
".gitmodules. ",
".gitmodules .",
".gitmodules..",
".gitmodules ",
".gitmodules. ",
".gitmodules . ",
".gitmodules .",
".Gitmodules ",
".Gitmodules.",
".Gitmodules ",
".Gitmodules. ",
".Gitmodules .",
".Gitmodules..",
".Gitmodules ",
".Gitmodules. ",
".Gitmodules . ",
".Gitmodules .",
"GITMOD~1",
"gitmod~1",
"GITMOD~2",
"gitmod~3",
"GITMOD~4",
"GITMOD~1 ",
"gitmod~2.",
"GITMOD~3 ",
"gitmod~4. ",
"GITMOD~1 .",
"gitmod~2 ",
"GITMOD~3. ",
"gitmod~4 . ",
"GI7EBA~1",
"gi7eba~9",
"GI7EB~10",
"GI7EB~11",
"GI7EB~99",
"GI7EB~10",
"GI7E~100",
"GI7E~101",
"GI7E~999",
"~1000000",
"~9999999",
};
static char *gitmodules_not_altnames[] = {
".gitmodules x",
".gitmodules .x",
" .gitmodules",
"..gitmodules",
"gitmodules",
".gitmodule",
".gitmodules x ",
".gitmodules .x",
"GI7EBA~",
"GI7EBA~0",
"GI7EBA~~1",
"GI7EBA~X",
"Gx7EBA~1",
"GI7EBX~1",
"GI7EB~1",
"GI7EB~01",
"GI7EB~1",
};
void test_path_dotgit__dotgit_modules(void)
{
size_t i;
cl_assert_equal_i(1, git_path_is_dotgit_modules(".gitmodules", strlen(".gitmodules")));
cl_assert_equal_i(1, git_path_is_dotgit_modules(".git\xe2\x80\x8cmodules", strlen(".git\xe2\x80\x8cmodules")));
for (i = 0; i < ARRAY_SIZE(gitmodules_altnames); i++) {
const char *name = gitmodules_altnames[i];
if (!git_path_is_dotgit_modules(name, strlen(name)))
cl_fail(name);
}
for (i = 0; i < ARRAY_SIZE(gitmodules_not_altnames); i++) {
const char *name = gitmodules_not_altnames[i];
if (git_path_is_dotgit_modules(name, strlen(name)))
cl_fail(name);
}
}
void test_path_dotgit__dotgit_modules_symlink(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, ".gitmodules", 0, GIT_PATH_REJECT_DOT_GIT_HFS|GIT_PATH_REJECT_DOT_GIT_NTFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS));
}
#include "clar_libgit2.h"
#include "posix.h"
#include "path.h"
#include "submodule_helpers.h"
#include "fileops.h"
#include "repository.h"
static git_repository *g_repo = NULL;
void test_submodule_escape__cleanup(void)
{
cl_git_sandbox_cleanup();
}
#define EVIL_SM_NAME "../../modules/evil"
#define EVIL_SM_NAME_WINDOWS "..\\\\..\\\\modules\\\\evil"
#define EVIL_SM_NAME_WINDOWS_UNESC "..\\..\\modules\\evil"
static int find_evil(git_submodule *sm, const char *name, void *payload)
{
int *foundit = (int *) payload;
GIT_UNUSED(sm);
if (!git__strcmp(EVIL_SM_NAME, name) ||
!git__strcmp(EVIL_SM_NAME_WINDOWS_UNESC, name))
*foundit = true;
return 0;
}
void test_submodule_escape__from_gitdir(void)
{
int foundit;
git_submodule *sm;
git_buf buf = GIT_BUF_INIT;
unsigned int sm_location;
g_repo = setup_fixture_submodule_simple();
cl_git_pass(git_buf_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules"));
cl_git_rewritefile(buf.ptr,
"[submodule \"" EVIL_SM_NAME "\"]\n"
" path = testrepo\n"
" url = ../testrepo.git\n");
git_buf_free(&buf);
/* Find it all the different ways we know about it */
foundit = 0;
cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit));
cl_assert_equal_i(0, foundit);
cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME));
/*
* We do know about this as it's in the index and HEAD, but the data is
* incomplete as there is no configured data for it (we pretend it
* doesn't exist). This leaves us with an odd situation but it's
* consistent with what we would do if we did add a submodule with no
* configuration.
*/
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
cl_git_pass(git_submodule_location(&sm_location, sm));
cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | GIT_SUBMODULE_STATUS_IN_HEAD, sm_location);
git_submodule_free(sm);
}
void test_submodule_escape__from_gitdir_windows(void)
{
int foundit;
git_submodule *sm;
git_buf buf = GIT_BUF_INIT;
unsigned int sm_location;
g_repo = setup_fixture_submodule_simple();
cl_git_pass(git_buf_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules"));
cl_git_rewritefile(buf.ptr,
"[submodule \"" EVIL_SM_NAME_WINDOWS "\"]\n"
" path = testrepo\n"
" url = ../testrepo.git\n");
git_buf_free(&buf);
/* Find it all the different ways we know about it */
foundit = 0;
cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit));
cl_assert_equal_i(0, foundit);
cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME_WINDOWS_UNESC));
/*
* We do know about this as it's in the index and HEAD, but the data is
* incomplete as there is no configured data for it (we pretend it
* doesn't exist). This leaves us with an odd situation but it's
* consistent with what we would do if we did add a submodule with no
* configuration.
*/
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
cl_git_pass(git_submodule_location(&sm_location, sm));
cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | GIT_SUBMODULE_STATUS_IN_HEAD, sm_location);
git_submodule_free(sm);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment