summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2021-04-24 09:44:45 +0100
committerEdward Thomson <ethomson@edwardthomson.com>2021-04-28 13:03:34 +0100
commite5851c62e2f467ebd49357a16bde5ed7d4f473c2 (patch)
treede06a04eac3193ab22ae04a0dd8f09064c6e07e3
parent1016ad4f98f1db7f5912c36b7922375d4c387ca0 (diff)
downloadlibgit2-e5851c62e2f467ebd49357a16bde5ed7d4f473c2.tar.gz
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.
-rw-r--r--src/refdb_fs.c84
-rw-r--r--tests/refs/basic.c40
2 files changed, 94 insertions, 30 deletions
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index 551732f6e..ad8068ed6 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -68,6 +68,35 @@ typedef struct refdb_fs_backend {
static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);
+GIT_INLINE(int) loose_path(
+ git_buf *out,
+ const char *base,
+ const char *refname)
+{
+ if (git_buf_joinpath(out, base, refname) < 0)
+ return -1;
+
+ return git_path_validate_filesystem_with_suffix(out->ptr, out->size,
+ CONST_STRLEN(".lock"));
+}
+
+GIT_INLINE(int) reflog_path(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname)
+{
+ const char *base;
+ int error;
+
+ base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir :
+ repo->commondir;
+
+ if ((error = git_buf_joinpath(out, base, GIT_REFLOG_DIR)) < 0)
+ return error;
+
+ return loose_path(out, out->ptr, refname);
+}
+
static int packref_cmp(const void *a_, const void *b_)
{
const struct packref *a = a_, *b = b_;
@@ -223,9 +252,8 @@ static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
{
int error;
- /* build full path to file */
- if ((error = git_buf_joinpath(buf, base, path)) < 0 ||
- (error = git_futils_readbuffer(buf, buf->ptr)) < 0)
+ if ((error = loose_path(buf, base, path)) < 0 ||
+ (error = git_futils_readbuffer(buf, buf->ptr)) < 0)
git_buf_dispose(buf);
return error;
@@ -336,7 +364,7 @@ static int refdb_fs_backend__exists(
*exists = 0;
- if ((error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0)
+ if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0)
goto out;
if (git_path_isfile(ref_path.ptr)) {
@@ -805,8 +833,8 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
return error;
- if (git_buf_joinpath(&ref_path, basedir, name) < 0)
- return -1;
+ if ((error = loose_path(&ref_path, basedir, name)) < 0)
+ return error;
filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
if (backend->fsync)
@@ -1329,6 +1357,9 @@ static int refdb_fs_backend__prune_refs(
backend->commonpath,
git_buf_cstr(&relative_path));
+ if (!error)
+ error = git_path_validate_filesystem(base_path.ptr, base_path.size);
+
if (error < 0)
goto cleanup;
@@ -1371,19 +1402,19 @@ static int refdb_fs_backend__delete(
static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
{
- git_buf loose_path = GIT_BUF_INIT;
+ git_buf path = GIT_BUF_INIT;
int error = 0;
- if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0)
- return -1;
+ if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0)
+ return error;
- error = p_unlink(loose_path.ptr);
+ error = p_unlink(path.ptr);
if (error < 0 && errno == ENOENT)
error = GIT_ENOTFOUND;
else if (error != 0)
error = -1;
- git_buf_dispose(&loose_path);
+ git_buf_dispose(&path);
return error;
}
@@ -1677,13 +1708,6 @@ static int create_new_reflog_file(const char *filepath)
return p_close(fd);
}
-GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
-{
- if (strcmp(name, GIT_HEAD_FILE) == 0)
- return git_buf_join3(path, '/', repo->gitdir, GIT_REFLOG_DIR, name);
- return git_buf_join3(path, '/', repo->commondir, GIT_REFLOG_DIR, name);
-}
-
static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
{
refdb_fs_backend *backend;
@@ -1696,7 +1720,7 @@ static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *
backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
repo = backend->repo;
- if ((error = retrieve_reflog_path(&path, repo, name)) < 0)
+ if ((error = reflog_path(&path, repo, name)) < 0)
return error;
error = create_new_reflog_file(git_buf_cstr(&path));
@@ -1710,7 +1734,7 @@ static int has_reflog(git_repository *repo, const char *name)
int ret = 0;
git_buf path = GIT_BUF_INIT;
- if (retrieve_reflog_path(&path, repo, name) < 0)
+ if (reflog_path(&path, repo, name) < 0)
goto cleanup;
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,
if (reflog_alloc(&log, name) < 0)
return -1;
- if (retrieve_reflog_path(&log_path, repo, name) < 0)
+ if (reflog_path(&log_path, repo, name) < 0)
goto cleanup;
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
return GIT_EINVALIDSPEC;
}
- if (retrieve_reflog_path(&log_path, repo, refname) < 0)
+ if (reflog_path(&log_path, repo, refname) < 0)
return -1;
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
if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
goto cleanup;
- if ((error = retrieve_reflog_path(&path, repo, ref->name)) < 0)
+ if ((error = reflog_path(&path, repo, ref->name)) < 0)
goto cleanup;
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_
if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
return -1;
- if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
- return -1;
+ if ((error = loose_path(&old_path, git_buf_cstr(&temp_path), old_name)) < 0)
+ return error;
- if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
- return -1;
+ if ((error = loose_path(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized))) < 0)
+ return error;
if (!git_path_exists(git_buf_cstr(&old_path))) {
error = GIT_ENOTFOUND;
@@ -2015,8 +2039,8 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
* - a/b -> a/b/c
* - a/b/c/d -> a/b/c
*/
- if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
- return -1;
+ if ((error = loose_path(&temp_path, git_buf_cstr(&temp_path), "temp_reflog")) < 0)
+ return error;
if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
error = -1;
@@ -2065,7 +2089,7 @@ static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name
GIT_ASSERT_ARG(_backend);
GIT_ASSERT_ARG(name);
- if ((error = retrieve_reflog_path(&path, backend->repo, name)) < 0)
+ if ((error = reflog_path(&path, backend->repo, name)) < 0)
goto out;
if (!git_path_exists(path.ptr))
diff --git a/tests/refs/basic.c b/tests/refs/basic.c
index ed0c0bde6..d1ec2bbe6 100644
--- a/tests/refs/basic.c
+++ b/tests/refs/basic.c
@@ -42,3 +42,43 @@ void test_refs_basic__reference_realloc(void)
git_reference_free(new_ref);
git_reference_free(ref);
}
+
+void test_refs_basic__longpaths(void)
+{
+#ifdef GIT_WIN32
+ const char *base;
+ size_t base_len, extra_len;
+ ssize_t remain_len, i;
+ git_buf refname = GIT_BUF_INIT;
+ git_reference *one = NULL, *two = NULL;
+ git_oid id;
+
+ cl_git_pass(git_oid_fromstr(&id, "099fabac3a9ea935598528c27f866e34089c2eff"));
+
+ base = git_repository_path(g_repo);
+ base_len = git_utf8_char_length(base, strlen(base));
+ extra_len = CONST_STRLEN("logs/refs/heads/") + CONST_STRLEN(".lock");
+
+ remain_len = (ssize_t)MAX_PATH - (base_len + extra_len);
+ cl_assert(remain_len > 0);
+
+ cl_git_pass(git_buf_puts(&refname, "refs/heads/"));
+
+ for (i = 0; i < remain_len; i++) {
+ cl_git_pass(git_buf_putc(&refname, 'a'));
+ }
+
+ /*
+ * The full path to the reflog lockfile is 260 characters,
+ * this is permitted.
+ */
+ cl_git_pass(git_reference_create(&one, g_repo, refname.ptr, &id, 0, NULL));
+
+ /* Adding one more character gives us a path that is too long. */
+ cl_git_pass(git_buf_putc(&refname, 'z'));
+ cl_git_fail(git_reference_create(&two, g_repo, refname.ptr, &id, 0, NULL));
+
+ git_reference_free(one);
+ git_reference_free(two);
+#endif
+}