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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
diff --git a/src/repository.c b/src/repository.c
index 87f5c1a..8da6ccb 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -187,7 +187,7 @@ void git_repository_free(git_repository *repo)
}
/* 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;
int error;
@@ -197,33 +197,52 @@ static int lookup_commondir(git_buf *out, git_buf *repository_path)
* common path, but it needs a trailing slash.
*/
if (!git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) {
- if ((error = git_buf_set(out, repository_path->ptr, repository_path->size)) == 0)
- error = git_path_to_dir(out);
+ if ((error = git_buf_set(commondir, repository_path->ptr, repository_path->size)) == 0)
+ error = git_path_to_dir(commondir);
+ *separate = false;
goto done;
}
+ *separate = true;
+
if ((error = git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 ||
(error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0)
goto done;
git_buf_rtrim(&common_link);
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;
} else {
- git_buf_swap(out, &common_link);
+ git_buf_swap(commondir, &common_link);
}
git_buf_dispose(&common_link);
/* 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:
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
*
@@ -231,11 +250,12 @@ done:
*/
static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf *common_path)
{
+ bool separate_commondir = false;
int error;
*out = false;
- if ((error = lookup_commondir(common_path, repository_path)) < 0)
+ if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0)
return error;
/* Ensure HEAD file exists */
@@ -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)
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;
return 0;
}
diff --git a/tests/repo/init.c b/tests/repo/init.c
index 01371ba..1aa326f 100644
--- a/tests/repo/init.c
+++ b/tests/repo/init.c
@@ -704,3 +704,35 @@ void test_repo_init__defaultbranch_config_empty(void)
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
+}