Skip to content

Commit c15ed35

Browse files
committed
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 parent 3589587 commit c15ed35

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

src/repository.c

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ void git_repository_free(git_repository *repo)
187187
}
188188

189189
/* Check if we have a separate commondir (e.g. we have a worktree) */
190-
static int lookup_commondir(git_buf *out, git_buf *repository_path)
190+
static int lookup_commondir(bool *separate, git_buf *commondir, git_buf *repository_path)
191191
{
192192
git_buf common_link = GIT_BUF_INIT;
193193
int error;
@@ -197,45 +197,65 @@ static int lookup_commondir(git_buf *out, git_buf *repository_path)
197197
* common path, but it needs a trailing slash.
198198
*/
199199
if (!git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) {
200-
if ((error = git_buf_set(out, repository_path->ptr, repository_path->size)) == 0)
201-
error = git_path_to_dir(out);
200+
if ((error = git_buf_set(commondir, repository_path->ptr, repository_path->size)) == 0)
201+
error = git_path_to_dir(commondir);
202202

203+
*separate = false;
203204
goto done;
204205
}
205206

207+
*separate = true;
208+
206209
if ((error = git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 ||
207210
(error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0)
208211
goto done;
209212

210213
git_buf_rtrim(&common_link);
211214
if (git_path_is_relative(common_link.ptr)) {
212-
if ((error = git_buf_joinpath(out, repository_path->ptr, common_link.ptr)) < 0)
215+
if ((error = git_buf_joinpath(commondir, repository_path->ptr, common_link.ptr)) < 0)
213216
goto done;
214217
} else {
215-
git_buf_swap(out, &common_link);
218+
git_buf_swap(commondir, &common_link);
216219
}
217220

218221
git_buf_dispose(&common_link);
219222

220223
/* Make sure the commondir path always has a trailing slash */
221-
error = git_path_prettify_dir(out, out->ptr, NULL);
224+
error = git_path_prettify_dir(commondir, commondir->ptr, NULL);
222225

223226
done:
224227
return error;
225228
}
226229

230+
GIT_INLINE(int) validate_repo_path(git_buf *path)
231+
{
232+
/*
233+
* The longest static path in a repository (or commondir) is the
234+
* packed refs file. (Loose refs may be longer since they
235+
* include the reference name, but will be validated when the
236+
* path is constructed.)
237+
*/
238+
static size_t suffix_len =
239+
CONST_STRLEN("objects/pack/pack-.pack.lock") +
240+
GIT_OID_HEXSZ;
241+
242+
return git_path_validate_filesystem_with_suffix(
243+
path->ptr, path->size, suffix_len);
244+
}
245+
227246
/*
228247
* Git repository open methods
229248
*
230249
* Open a repository object from its path
231250
*/
232251
static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf *common_path)
233252
{
253+
bool separate_commondir = false;
234254
int error;
235255

236256
*out = false;
237257

238-
if ((error = lookup_commondir(common_path, repository_path)) < 0)
258+
if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0)
239259
return error;
240260

241261
/* Ensure HEAD file exists */
@@ -248,6 +268,12 @@ static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf
248268
if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false)
249269
return 0;
250270

271+
/* Ensure the repo (and commondir) are valid paths */
272+
if ((error = validate_repo_path(common_path)) < 0 ||
273+
(separate_commondir &&
274+
(error = validate_repo_path(repository_path)) < 0))
275+
return error;
276+
251277
*out = true;
252278
return 0;
253279
}

tests/repo/init.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,3 +704,35 @@ void test_repo_init__defaultbranch_config_empty(void)
704704

705705
git_reference_free(head);
706706
}
707+
708+
void test_repo_init__longpath(void)
709+
{
710+
#ifdef GIT_WIN32
711+
size_t padding = CONST_STRLEN("objects/pack/pack-.pack.lock") + GIT_OID_HEXSZ;
712+
size_t max, i;
713+
git_buf path = GIT_BUF_INIT;
714+
git_repository *one = NULL, *two = NULL;
715+
716+
/*
717+
* Files within repositories need to fit within MAX_PATH;
718+
* that means a repo path must be at most (MAX_PATH - 18).
719+
*/
720+
cl_git_pass(git_buf_puts(&path, clar_sandbox_path()));
721+
cl_git_pass(git_buf_putc(&path, '/'));
722+
723+
max = ((MAX_PATH) - path.size) - padding;
724+
725+
for (i = 0; i < max - 1; i++)
726+
cl_git_pass(git_buf_putc(&path, 'a'));
727+
728+
cl_git_pass(git_repository_init(&one, path.ptr, 1));
729+
730+
/* Paths longer than this are rejected */
731+
cl_git_pass(git_buf_putc(&path, 'z'));
732+
cl_git_fail(git_repository_init(&two, path.ptr, 1));
733+
734+
git_repository_free(one);
735+
git_repository_free(two);
736+
git_buf_dispose(&path);
737+
#endif
738+
}

0 commit comments

Comments
 (0)