diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2021-04-24 09:44:45 +0100 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2021-04-28 13:03:34 +0100 |
commit | e5851c62e2f467ebd49357a16bde5ed7d4f473c2 (patch) | |
tree | de06a04eac3193ab22ae04a0dd8f09064c6e07e3 | |
parent | 1016ad4f98f1db7f5912c36b7922375d4c387ca0 (diff) | |
download | libgit2-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.c | 84 | ||||
-rw-r--r-- | tests/refs/basic.c | 40 |
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 +} |