Commit c15ed350 by Edward Thomson

repo: validate repository paths

Ensure that a repository's path (at initialization or open time) is
valid.  On Windows systems, this means that the longest known path
beneath the repository will fit within MAX_PATH: this is a lock file for
a loose object within the repository itself.

Other paths, like a very long loose reference, may fail to be opened
after the repository is opened.  These variable length paths will be
checked when they are accessed themselves.  This new functionality is
done at open to prevent needlessly checking every file in the gitdir
(eg, `MERGE_HEAD`) for its length when we could instead check once at
repository open time.
parent 3589587d
...@@ -187,7 +187,7 @@ void git_repository_free(git_repository *repo) ...@@ -187,7 +187,7 @@ void git_repository_free(git_repository *repo)
} }
/* Check if we have a separate commondir (e.g. we have a worktree) */ /* Check if we have a separate commondir (e.g. we have a worktree) */
static int lookup_commondir(git_buf *out, git_buf *repository_path) static int lookup_commondir(bool *separate, git_buf *commondir, git_buf *repository_path)
{ {
git_buf common_link = GIT_BUF_INIT; git_buf common_link = GIT_BUF_INIT;
int error; int error;
...@@ -197,33 +197,52 @@ static int lookup_commondir(git_buf *out, git_buf *repository_path) ...@@ -197,33 +197,52 @@ static int lookup_commondir(git_buf *out, git_buf *repository_path)
* common path, but it needs a trailing slash. * common path, but it needs a trailing slash.
*/ */
if (!git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { if (!git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) {
if ((error = git_buf_set(out, repository_path->ptr, repository_path->size)) == 0) if ((error = git_buf_set(commondir, repository_path->ptr, repository_path->size)) == 0)
error = git_path_to_dir(out); error = git_path_to_dir(commondir);
*separate = false;
goto done; goto done;
} }
*separate = true;
if ((error = git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 || if ((error = git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 ||
(error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0) (error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0)
goto done; goto done;
git_buf_rtrim(&common_link); git_buf_rtrim(&common_link);
if (git_path_is_relative(common_link.ptr)) { if (git_path_is_relative(common_link.ptr)) {
if ((error = git_buf_joinpath(out, repository_path->ptr, common_link.ptr)) < 0) if ((error = git_buf_joinpath(commondir, repository_path->ptr, common_link.ptr)) < 0)
goto done; goto done;
} else { } else {
git_buf_swap(out, &common_link); git_buf_swap(commondir, &common_link);
} }
git_buf_dispose(&common_link); git_buf_dispose(&common_link);
/* Make sure the commondir path always has a trailing slash */ /* Make sure the commondir path always has a trailing slash */
error = git_path_prettify_dir(out, out->ptr, NULL); error = git_path_prettify_dir(commondir, commondir->ptr, NULL);
done: done:
return error; return error;
} }
GIT_INLINE(int) validate_repo_path(git_buf *path)
{
/*
* The longest static path in a repository (or commondir) is the
* packed refs file. (Loose refs may be longer since they
* include the reference name, but will be validated when the
* path is constructed.)
*/
static size_t suffix_len =
CONST_STRLEN("objects/pack/pack-.pack.lock") +
GIT_OID_HEXSZ;
return git_path_validate_filesystem_with_suffix(
path->ptr, path->size, suffix_len);
}
/* /*
* Git repository open methods * Git repository open methods
* *
...@@ -231,11 +250,12 @@ done: ...@@ -231,11 +250,12 @@ done:
*/ */
static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf *common_path) static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf *common_path)
{ {
bool separate_commondir = false;
int error; int error;
*out = false; *out = false;
if ((error = lookup_commondir(common_path, repository_path)) < 0) if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0)
return error; return error;
/* Ensure HEAD file exists */ /* Ensure HEAD file exists */
...@@ -248,6 +268,12 @@ static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf ...@@ -248,6 +268,12 @@ static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf
if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false) if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false)
return 0; return 0;
/* Ensure the repo (and commondir) are valid paths */
if ((error = validate_repo_path(common_path)) < 0 ||
(separate_commondir &&
(error = validate_repo_path(repository_path)) < 0))
return error;
*out = true; *out = true;
return 0; return 0;
} }
......
...@@ -704,3 +704,35 @@ void test_repo_init__defaultbranch_config_empty(void) ...@@ -704,3 +704,35 @@ void test_repo_init__defaultbranch_config_empty(void)
git_reference_free(head); git_reference_free(head);
} }
void test_repo_init__longpath(void)
{
#ifdef GIT_WIN32
size_t padding = CONST_STRLEN("objects/pack/pack-.pack.lock") + GIT_OID_HEXSZ;
size_t max, i;
git_buf path = GIT_BUF_INIT;
git_repository *one = NULL, *two = NULL;
/*
* Files within repositories need to fit within MAX_PATH;
* that means a repo path must be at most (MAX_PATH - 18).
*/
cl_git_pass(git_buf_puts(&path, clar_sandbox_path()));
cl_git_pass(git_buf_putc(&path, '/'));
max = ((MAX_PATH) - path.size) - padding;
for (i = 0; i < max - 1; i++)
cl_git_pass(git_buf_putc(&path, 'a'));
cl_git_pass(git_repository_init(&one, path.ptr, 1));
/* Paths longer than this are rejected */
cl_git_pass(git_buf_putc(&path, 'z'));
cl_git_fail(git_repository_init(&two, path.ptr, 1));
git_repository_free(one);
git_repository_free(two);
git_buf_dispose(&path);
#endif
}
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