Skip to content

Commit e5851c6

Browse files
committed
refs: ensure loose refs adhere to path validation
On Windows, we need to enforce MAX_PATH for loose references and their reflogs. Ensure that any path - including the lock file - would fit within the 260 character maximum. We do not honor core.longpaths for loose reference files or reflogs. core.longpaths only applies to paths in the working directory.
1 parent 1016ad4 commit e5851c6

File tree

2 files changed

+94
-30
lines changed

2 files changed

+94
-30
lines changed

src/refdb_fs.c

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,35 @@ typedef struct refdb_fs_backend {
6868

6969
static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);
7070

71+
GIT_INLINE(int) loose_path(
72+
git_buf *out,
73+
const char *base,
74+
const char *refname)
75+
{
76+
if (git_buf_joinpath(out, base, refname) < 0)
77+
return -1;
78+
79+
return git_path_validate_filesystem_with_suffix(out->ptr, out->size,
80+
CONST_STRLEN(".lock"));
81+
}
82+
83+
GIT_INLINE(int) reflog_path(
84+
git_buf *out,
85+
git_repository *repo,
86+
const char *refname)
87+
{
88+
const char *base;
89+
int error;
90+
91+
base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir :
92+
repo->commondir;
93+
94+
if ((error = git_buf_joinpath(out, base, GIT_REFLOG_DIR)) < 0)
95+
return error;
96+
97+
return loose_path(out, out->ptr, refname);
98+
}
99+
71100
static int packref_cmp(const void *a_, const void *b_)
72101
{
73102
const struct packref *a = a_, *b = b_;
@@ -223,9 +252,8 @@ static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
223252
{
224253
int error;
225254

226-
/* build full path to file */
227-
if ((error = git_buf_joinpath(buf, base, path)) < 0 ||
228-
(error = git_futils_readbuffer(buf, buf->ptr)) < 0)
255+
if ((error = loose_path(buf, base, path)) < 0 ||
256+
(error = git_futils_readbuffer(buf, buf->ptr)) < 0)
229257
git_buf_dispose(buf);
230258

231259
return error;
@@ -336,7 +364,7 @@ static int refdb_fs_backend__exists(
336364

337365
*exists = 0;
338366

339-
if ((error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0)
367+
if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0)
340368
goto out;
341369

342370
if (git_path_isfile(ref_path.ptr)) {
@@ -805,8 +833,8 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
805833
if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
806834
return error;
807835

808-
if (git_buf_joinpath(&ref_path, basedir, name) < 0)
809-
return -1;
836+
if ((error = loose_path(&ref_path, basedir, name)) < 0)
837+
return error;
810838

811839
filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
812840
if (backend->fsync)
@@ -1329,6 +1357,9 @@ static int refdb_fs_backend__prune_refs(
13291357
backend->commonpath,
13301358
git_buf_cstr(&relative_path));
13311359

1360+
if (!error)
1361+
error = git_path_validate_filesystem(base_path.ptr, base_path.size);
1362+
13321363
if (error < 0)
13331364
goto cleanup;
13341365

@@ -1371,19 +1402,19 @@ static int refdb_fs_backend__delete(
13711402

13721403
static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
13731404
{
1374-
git_buf loose_path = GIT_BUF_INIT;
1405+
git_buf path = GIT_BUF_INIT;
13751406
int error = 0;
13761407

1377-
if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0)
1378-
return -1;
1408+
if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0)
1409+
return error;
13791410

1380-
error = p_unlink(loose_path.ptr);
1411+
error = p_unlink(path.ptr);
13811412
if (error < 0 && errno == ENOENT)
13821413
error = GIT_ENOTFOUND;
13831414
else if (error != 0)
13841415
error = -1;
13851416

1386-
git_buf_dispose(&loose_path);
1417+
git_buf_dispose(&path);
13871418

13881419
return error;
13891420
}
@@ -1677,13 +1708,6 @@ static int create_new_reflog_file(const char *filepath)
16771708
return p_close(fd);
16781709
}
16791710

1680-
GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
1681-
{
1682-
if (strcmp(name, GIT_HEAD_FILE) == 0)
1683-
return git_buf_join3(path, '/', repo->gitdir, GIT_REFLOG_DIR, name);
1684-
return git_buf_join3(path, '/', repo->commondir, GIT_REFLOG_DIR, name);
1685-
}
1686-
16871711
static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
16881712
{
16891713
refdb_fs_backend *backend;
@@ -1696,7 +1720,7 @@ static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *
16961720
backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
16971721
repo = backend->repo;
16981722

1699-
if ((error = retrieve_reflog_path(&path, repo, name)) < 0)
1723+
if ((error = reflog_path(&path, repo, name)) < 0)
17001724
return error;
17011725

17021726
error = create_new_reflog_file(git_buf_cstr(&path));
@@ -1710,7 +1734,7 @@ static int has_reflog(git_repository *repo, const char *name)
17101734
int ret = 0;
17111735
git_buf path = GIT_BUF_INIT;
17121736

1713-
if (retrieve_reflog_path(&path, repo, name) < 0)
1737+
if (reflog_path(&path, repo, name) < 0)
17141738
goto cleanup;
17151739

17161740
ret = git_path_isfile(git_buf_cstr(&path));
@@ -1751,7 +1775,7 @@ static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend,
17511775
if (reflog_alloc(&log, name) < 0)
17521776
return -1;
17531777

1754-
if (retrieve_reflog_path(&log_path, repo, name) < 0)
1778+
if (reflog_path(&log_path, repo, name) < 0)
17551779
goto cleanup;
17561780

17571781
error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
@@ -1833,7 +1857,7 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
18331857
return GIT_EINVALIDSPEC;
18341858
}
18351859

1836-
if (retrieve_reflog_path(&log_path, repo, refname) < 0)
1860+
if (reflog_path(&log_path, repo, refname) < 0)
18371861
return -1;
18381862

18391863
if (!git_path_isfile(git_buf_cstr(&log_path))) {
@@ -1934,7 +1958,7 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co
19341958
if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
19351959
goto cleanup;
19361960

1937-
if ((error = retrieve_reflog_path(&path, repo, ref->name)) < 0)
1961+
if ((error = reflog_path(&path, repo, ref->name)) < 0)
19381962
goto cleanup;
19391963

19401964
if (((error = git_futils_mkpath2file(git_buf_cstr(&path), 0777)) < 0) &&
@@ -1997,11 +2021,11 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
19972021
if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
19982022
return -1;
19992023

2000-
if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
2001-
return -1;
2024+
if ((error = loose_path(&old_path, git_buf_cstr(&temp_path), old_name)) < 0)
2025+
return error;
20022026

2003-
if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
2004-
return -1;
2027+
if ((error = loose_path(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized))) < 0)
2028+
return error;
20052029

20062030
if (!git_path_exists(git_buf_cstr(&old_path))) {
20072031
error = GIT_ENOTFOUND;
@@ -2015,8 +2039,8 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
20152039
* - a/b -> a/b/c
20162040
* - a/b/c/d -> a/b/c
20172041
*/
2018-
if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
2019-
return -1;
2042+
if ((error = loose_path(&temp_path, git_buf_cstr(&temp_path), "temp_reflog")) < 0)
2043+
return error;
20202044

20212045
if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
20222046
error = -1;
@@ -2065,7 +2089,7 @@ static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name
20652089
GIT_ASSERT_ARG(_backend);
20662090
GIT_ASSERT_ARG(name);
20672091

2068-
if ((error = retrieve_reflog_path(&path, backend->repo, name)) < 0)
2092+
if ((error = reflog_path(&path, backend->repo, name)) < 0)
20692093
goto out;
20702094

20712095
if (!git_path_exists(path.ptr))

tests/refs/basic.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,43 @@ void test_refs_basic__reference_realloc(void)
4242
git_reference_free(new_ref);
4343
git_reference_free(ref);
4444
}
45+
46+
void test_refs_basic__longpaths(void)
47+
{
48+
#ifdef GIT_WIN32
49+
const char *base;
50+
size_t base_len, extra_len;
51+
ssize_t remain_len, i;
52+
git_buf refname = GIT_BUF_INIT;
53+
git_reference *one = NULL, *two = NULL;
54+
git_oid id;
55+
56+
cl_git_pass(git_oid_fromstr(&id, "099fabac3a9ea935598528c27f866e34089c2eff"));
57+
58+
base = git_repository_path(g_repo);
59+
base_len = git_utf8_char_length(base, strlen(base));
60+
extra_len = CONST_STRLEN("logs/refs/heads/") + CONST_STRLEN(".lock");
61+
62+
remain_len = (ssize_t)MAX_PATH - (base_len + extra_len);
63+
cl_assert(remain_len > 0);
64+
65+
cl_git_pass(git_buf_puts(&refname, "refs/heads/"));
66+
67+
for (i = 0; i < remain_len; i++) {
68+
cl_git_pass(git_buf_putc(&refname, 'a'));
69+
}
70+
71+
/*
72+
* The full path to the reflog lockfile is 260 characters,
73+
* this is permitted.
74+
*/
75+
cl_git_pass(git_reference_create(&one, g_repo, refname.ptr, &id, 0, NULL));
76+
77+
/* Adding one more character gives us a path that is too long. */
78+
cl_git_pass(git_buf_putc(&refname, 'z'));
79+
cl_git_fail(git_reference_create(&two, g_repo, refname.ptr, &id, 0, NULL));
80+
81+
git_reference_free(one);
82+
git_reference_free(two);
83+
#endif
84+
}

0 commit comments

Comments
 (0)