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