[PATCH 04/12] Protect index with sparse prefix

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This ensures no one can write to index outside sparse prefix.
Protection is usually applied to the_index only. For more
complicated cases where temporary index is required, thorough
check will be done by check_index_prefix()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@xxxxxxxxx>
---
 builtin-commit.c          |   11 +++++
 builtin-ls-files.c        |    2 +
 builtin-merge-recursive.c |    4 +-
 builtin-read-tree.c       |    5 ++
 cache.h                   |    1 +
 diff-lib.c                |    3 +
 read-cache.c              |  107 ++++++++++++++++++++++++++++++++++++++++++++-
 unpack-trees.c            |    3 +
 unpack-trees.h            |    3 +-
 9 files changed, 135 insertions(+), 4 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index 97e64de..f2d301a 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -223,6 +223,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 	int fd;
 	struct string_list partial;
 	const char **pathspec = NULL;
+	char **saved_sparse_prefix;
 
 	if (interactive) {
 		interactive_add(argc, argv, prefix);
@@ -306,6 +307,13 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 
 	memset(&partial, 0, sizeof(partial));
 	partial.strdup_strings = 1;
+
+	/*
+	 * this code modifies index but won't write down anything
+	 * so it's safe to turn of sparse_prefix protection for a while
+	 */
+	saved_sparse_prefix = save_sparse_prefix();
+
 	if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
 		exit(1);
 
@@ -313,6 +321,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 	if (read_cache() < 0)
 		die("cannot read the index");
 
+	/* re-enable sparse_prefix again */
+	restore_sparse_prefix(saved_sparse_prefix);
+
 	fd = hold_locked_index(&index_lock, 1);
 	add_remove_files(&partial);
 	refresh_cache(REFRESH_QUIET);
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index e8d568e..4d18873 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -606,6 +606,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 		 */
 		if (show_stage || show_unmerged)
 			die("ls-files --with-tree is incompatible with -s or -u");
+		/* no need to restore as ls-files never writes index */
+		save_sparse_prefix();
 		overlay_tree_on_cache(with_tree, prefix);
 	}
 	show_files(&dir, prefix);
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 43e55bf..7ce015b 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -201,8 +201,10 @@ static int git_merge_trees(int index_only,
 	memset(&opts, 0, sizeof(opts));
 	if (index_only)
 		opts.index_only = 1;
-	else
+	else {
 		opts.update = 1;
+		opts.check_index_prefix = 1;
+	}
 	opts.merge = 1;
 	opts.head_idx = 2;
 	opts.fn = threeway_merge;
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 72a6de3..0883b41 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -218,6 +218,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
 			opts.head_idx = 1;
 	}
 
+	if (get_sparse_prefix()) {
+		read_cache();
+		opts.check_index_prefix = 1;
+	}
+
 	for (i = 0; i < nr_trees; i++) {
 		struct tree *tree = trees[i];
 		parse_tree(tree);
diff --git a/cache.h b/cache.h
index 4687096..f6bbadc 100644
--- a/cache.h
+++ b/cache.h
@@ -331,6 +331,7 @@ extern char **split_prefix(const char *env);
 extern char **combine_prefix_list(char **p1, char **p2);
 extern void free_prefix_list(char **prefix_list);
 extern int outside_prefix_list(char **iprefix, const char *prefix);
+extern int check_index_prefix(const struct index_state *index, int is_merge);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
diff --git a/diff-lib.c b/diff-lib.c
index e7eaff9..2908146 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -73,6 +73,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 		struct cache_entry *ce = active_cache[i];
 		int changed;
 
+		if (index_outside_sparse_prefix(ce->name))
+			continue;
+
 		if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
 			DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
 			break;
diff --git a/read-cache.c b/read-cache.c
index a50a851..4fea40e 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -23,6 +23,11 @@
 
 struct index_state the_index;
 
+static int outside_cache_prefix(const struct index_state *istate, const char *ce_name)
+{
+	return istate == &the_index && index_outside_sparse_prefix(ce_name);
+}
+
 static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
 {
 	istate->cache[nr] = ce;
@@ -396,6 +401,8 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 {
 	struct cache_entry *ce = istate->cache[pos];
 
+	if (outside_cache_prefix(istate, ce->name))
+		die("%s: cannot remove from index outside %s", ce->name, get_sparse_prefix_str());
 	remove_name_hash(ce);
 	istate->cache_changed = 1;
 	istate->cache_nr--;
@@ -410,6 +417,10 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 int remove_file_from_index(struct index_state *istate, const char *path)
 {
 	int pos = index_name_pos(istate, path, strlen(path));
+
+	if (outside_cache_prefix(istate, path))
+		die("%s: cannot remove from index from outside %s", path, get_sparse_prefix_str());
+
 	if (pos < 0)
 		pos = -pos-1;
 	cache_tree_invalidate_path(istate->cache_tree, path);
@@ -809,21 +820,43 @@ static int check_file_directory_conflict(struct index_state *istate,
 	return retval + has_dir_name(istate, ce, pos, ok_to_replace);
 }
 
+/*
+ * ce_compare only considers stuff that will be written to a tree object
+ * or index stages
+ *
+ * On CE_* flags CE_HASHED, CE_UNHASHED and CE_UPTODATE are out because they
+ * are in-core only, will not be written out
+ */
+#define CE_COMPARE_MASK (CE_STATE_MASK | CE_UPTODATE)
+static int ce_compare(const struct cache_entry *ce1, const struct cache_entry *ce2)
+{
+	return ce1->ce_mode == ce2->ce_mode &&
+		((ce1->ce_flags ^ ce2->ce_flags) & ~CE_COMPARE_MASK) == 0 &&
+		!memcmp(ce1->sha1, ce2->sha1, 20) &&
+		!strcmp(ce1->name, ce2->name);
+}
+
 static int add_index_entry_with_check(struct index_state *istate, struct cache_entry *ce, int option)
 {
 	int pos;
 	int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
 	int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
 	int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
+	int is_outside = outside_cache_prefix(istate, ce->name);
 
 	cache_tree_invalidate_path(istate->cache_tree, ce->name);
 	pos = index_name_pos(istate, ce->name, ce->ce_flags);
 
 	/* existing match? Just replace it. */
 	if (pos >= 0) {
+		if (is_outside && !ce_compare(istate->cache[pos], ce))
+			die("%s: cannot add to index outside %s", ce->name, get_sparse_prefix_str());
 		replace_index_entry(istate, pos, ce);
 		return 0;
 	}
+
+	if (is_outside)
+		die("%s: cannot add to index outside %s", ce->name, get_sparse_prefix_str());
 	pos = -pos-1;
 
 	/*
@@ -858,9 +891,11 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
 {
 	int pos;
 
-	if (option & ADD_CACHE_JUST_APPEND)
+	if (option & ADD_CACHE_JUST_APPEND) {
+		if (outside_cache_prefix(istate, ce->name))
+			die("%s: cannot add to index outside %s", ce->name, get_sparse_prefix_str());
 		pos = istate->cache_nr;
-	else {
+	} else {
 		int ret;
 		ret = add_index_entry_with_check(istate, ce, option);
 		if (ret <= 0)
@@ -1444,3 +1479,71 @@ int read_index_unmerged(struct index_state *istate)
 	istate->cache_nr = dst - istate->cache;
 	return !!last;
 }
+
+int check_index_prefix(const struct index_state *index, int is_merge)
+{
+	unsigned i;
+	char **index_prefix = get_sparse_prefix();
+
+	if (!index_prefix)
+		return 1;
+
+	/* Check for unmerged entries first */
+	for (i = 0; i < index->cache_nr; i++) {
+		struct cache_entry *ce = index->cache[i];
+		if (ce_stage(ce) && index_outside_sparse_prefix(ce->name))
+			return 0;
+	}
+
+	/*
+	 * if is_merge is true, caller has decided that there
+	 * could be changes outside index prefix, return now
+	 */
+	if (is_merge)
+		return 1;
+	else {
+		const struct index_state *idx[2];
+		int prefix_i[2];
+		int index_prefix_nr;
+		unsigned entry_i[2];
+
+		idx[0] = &the_index;
+		idx[1] = index;
+		prefix_i[0] = prefix_i[1] = 0;
+		entry_i[0] = entry_i[1] = 0;
+		index_prefix_nr = 0;
+		while (index_prefix[index_prefix_nr])
+			index_prefix_nr++;
+
+		while (entry_i[0] < idx[0]->cache_nr &&
+		       entry_i[1] < idx[1]->cache_nr) {
+			/* ignore anything inside index prefix */
+			int what_index;
+			for (what_index = 0; what_index < 2; what_index++) {
+				const struct index_state *idx_p = idx[what_index];
+				const char *prefix = index_prefix[prefix_i[what_index]];
+				unsigned *entry_ip = entry_i+what_index;
+				if (prefix_i[what_index] < index_prefix_nr &&
+				    !prefixcmp(idx_p->cache[*entry_ip]->name, prefix)) {
+					while (*entry_ip < idx_p->cache_nr &&
+					       !prefixcmp(idx_p->cache[*entry_ip]->name, prefix))
+						entry_ip[0]++;
+					prefix_i[what_index]++;
+				}
+			}
+
+			if (entry_i[0] == idx[0]->cache_nr || entry_i[1] == idx[1]->cache_nr)
+				break;
+
+			/* does not match */
+			if (!ce_compare(idx[0]->cache[entry_i[0]], idx[1]->cache[entry_i[1]]))
+				return 0;
+
+			entry_i[0]++;
+			entry_i[1]++;
+		}
+
+		return entry_i[0] == idx[0]->cache_nr &&
+			entry_i[1] == idx[1]->cache_nr;
+	}
+}
diff --git a/unpack-trees.c b/unpack-trees.c
index cba0aca..0a6723b 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -408,6 +408,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 	if (o->trivial_merges_only && o->nontrivial_merge)
 		return unpack_failed(o, "Merge requires file-level merging");
 
+	if (o->check_index_prefix && !check_index_prefix(&o->result, o->merge && !o->prefix))
+		return unpack_failed(o, "Merge outside index prefix");
+
 	o->src_index = NULL;
 	ret = check_updates(o) ? (-2) : 0;
 	if (o->dst_index)
diff --git a/unpack-trees.h b/unpack-trees.h
index 94e5672..a1b46f9 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -26,7 +26,8 @@ struct unpack_trees_options {
 		     verbose_update:1,
 		     aggressive:1,
 		     skip_unmerged:1,
-		     gently:1;
+		     gently:1,
+		     check_index_prefix:1;
 	const char *prefix;
 	int pos;
 	struct dir_struct *dir;
-- 
1.5.5.GIT
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux