Commit 9fb755d561e149b81950c7cc7d8cb5131b24079b

Edward Thomson 2021-04-04T19:59:57

attr: validate workdir paths for attribute files We should allow attribute files - inside working directories - to have names longer than MAX_PATH when core.longpaths is set. `git_attr_path__init` takes a repository to validate the path with.

diff --git a/src/attr.c b/src/attr.c
index e85e271..f9cd930 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -67,7 +67,7 @@ int git_attr_get(
 	if (git_repository_is_bare(repo))
 		dir_flag = GIT_DIR_FLAG_FALSE;
 
-	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
+	if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
 		return -1;
 
 	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
@@ -133,7 +133,7 @@ int git_attr_get_many_with_session(
 	if (git_repository_is_bare(repo))
 		dir_flag = GIT_DIR_FLAG_FALSE;
 
-	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
+	if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
 		return -1;
 
 	if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
@@ -217,7 +217,7 @@ int git_attr_foreach(
 	if (git_repository_is_bare(repo))
 		dir_flag = GIT_DIR_FLAG_FALSE;
 
-	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
+	if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
 		return -1;
 
 	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
diff --git a/src/attr_file.c b/src/attr_file.c
index adc56d5..d1b90c5 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -403,7 +403,7 @@ int git_attr_file__load_standalone(git_attr_file **out, const char *path)
 
 	if ((error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE)) < 0 ||
 	    (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 ||
-	    (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, path, &file->pool)) < 0)
+	    (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0)
 		goto out;
 
 	*out = file;
@@ -503,14 +503,19 @@ git_attr_assignment *git_attr_rule__lookup_assignment(
 }
 
 int git_attr_path__init(
-	git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
+	git_attr_path *info,
+	git_repository *repo,
+	const char *path,
+	const char *base,
+	git_dir_flag dir_flag)
 {
 	ssize_t root;
 
 	/* build full path as best we can */
 	git_buf_init(&info->full, 0);
 
-	if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
+	if (git_path_join_unrooted(&info->full, path, base, &root) < 0 ||
+	    git_path_validate_workdir_buf(repo, &info->full) < 0)
 		return -1;
 
 	info->path = info->full.ptr + root;
diff --git a/src/attr_file.h b/src/attr_file.h
index 2b6b1d6..617436b 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -207,8 +207,11 @@ extern git_attr_assignment *git_attr_rule__lookup_assignment(
 typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag;
 
 extern int git_attr_path__init(
-	git_attr_path *info, const char *path, const char *base, git_dir_flag is_dir);
-
+	git_attr_path *out,
+	git_repository *repo,
+	const char *path,
+	const char *base,
+	git_dir_flag is_dir);
 extern void git_attr_path__free(git_attr_path *info);
 
 extern int git_attr_assignment__parse(
diff --git a/src/attrcache.c b/src/attrcache.c
index 2485b05..32513da 100644
--- a/src/attrcache.c
+++ b/src/attrcache.c
@@ -38,6 +38,7 @@ GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
 
 int git_attr_cache__alloc_file_entry(
 	git_attr_file_entry **out,
+	git_repository *repo,
 	const char *base,
 	const char *path,
 	git_pool *pool)
@@ -65,6 +66,9 @@ int git_attr_cache__alloc_file_entry(
 	}
 	memcpy(&ce->fullpath[baselen], path, pathlen);
 
+	if (git_path_validate_workdir_with_len(repo, ce->fullpath, pathlen + baselen) < 0)
+		return -1;
+
 	ce->path = &ce->fullpath[baselen];
 	*out = ce;
 
@@ -79,8 +83,8 @@ static int attr_cache_make_entry(
 	git_attr_file_entry *entry = NULL;
 	int error;
 
-	if ((error = git_attr_cache__alloc_file_entry(&entry, git_repository_workdir(repo),
-						      path, &cache->pool)) < 0)
+	if ((error = git_attr_cache__alloc_file_entry(&entry, repo,
+		git_repository_workdir(repo), path, &cache->pool)) < 0)
 		return error;
 
 	if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0)
@@ -169,7 +173,8 @@ static int attr_cache_lookup(
 	if (base != NULL && git_path_root(filename) < 0) {
 		git_buf *p = attr_session ? &attr_session->tmp : &path;
 
-		if (git_buf_joinpath(p, base, filename) < 0)
+		if (git_buf_joinpath(p, base, filename) < 0 ||
+		    git_path_validate_workdir_buf(repo, p) < 0)
 			return -1;
 
 		filename = p->ptr;
diff --git a/src/attrcache.h b/src/attrcache.h
index 4b1d5ce..5e2fb29 100644
--- a/src/attrcache.h
+++ b/src/attrcache.h
@@ -44,6 +44,7 @@ extern bool git_attr_cache__is_cached(
 
 extern int git_attr_cache__alloc_file_entry(
 	git_attr_file_entry **out,
+	git_repository *repo,
 	const char *base,
 	const char *path,
 	git_pool *pool);
diff --git a/src/ignore.c b/src/ignore.c
index 27a650b..085b0e9 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -453,7 +453,7 @@ int git_ignore__lookup(
 	*out = GIT_IGNORE_NOTFOUND;
 
 	if (git_attr_path__init(
-		&path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
+		&path, ignores->repo, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
 		return -1;
 
 	/* first process builtins - success means path was found */
@@ -537,7 +537,7 @@ int git_ignore_path_is_ignored(
 	else if (git_repository_is_bare(repo))
 		dir_flag = GIT_DIR_FLAG_FALSE;
 
-	if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 ||
+	if ((error = git_attr_path__init(&path, repo, pathname, workdir, dir_flag)) < 0 ||
 		(error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
 		goto cleanup;
 
diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c
index 6063468..29a66b0 100644
--- a/tests/attr/lookup.c
+++ b/tests/attr/lookup.c
@@ -13,7 +13,7 @@ void test_attr_lookup__simple(void)
 	cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path);
 	cl_assert(file->rules.length == 1);
 
-	cl_git_pass(git_attr_path__init(&path, "test", NULL, GIT_DIR_FLAG_UNKNOWN));
+	cl_git_pass(git_attr_path__init(&path, NULL, "test", NULL, GIT_DIR_FLAG_UNKNOWN));
 	cl_assert_equal_s("test", path.path);
 	cl_assert_equal_s("test", path.basename);
 	cl_assert(!path.is_dir);
@@ -36,7 +36,7 @@ static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int
 	int error;
 
 	for (c = cases; c->path != NULL; c++) {
-		cl_git_pass(git_attr_path__init(&path, c->path, NULL, GIT_DIR_FLAG_UNKNOWN));
+		cl_git_pass(git_attr_path__init(&path, NULL, c->path, NULL, GIT_DIR_FLAG_UNKNOWN));
 
 		if (force_dir)
 			path.is_dir = 1;
@@ -133,7 +133,7 @@ void test_attr_lookup__match_variants(void)
 	cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path);
 	cl_assert(file->rules.length == 10);
 
-	cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN));
+	cl_git_pass(git_attr_path__init(&path, NULL, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN));
 	cl_assert_equal_s("pat0", path.basename);
 
 	run_test_cases(file, cases, 0);