Skip to content

Commit 59bf8d2

Browse files
ttaylorrdscho
authored andcommitted
Merge branch 'ps/cache-tree-w-broken-index-entry' into next
Fail gracefully instead of crashing when attempting to write the contents of a corrupt in-core index as a tree object. * ps/cache-tree-w-broken-index-entry: unpack-trees: detect mismatching number of cache-tree/index entries cache-tree: detect mismatching number of index entries cache-tree: refactor verification to return error codes
2 parents 784986f + c68fa89 commit 59bf8d2

File tree

5 files changed

+97
-43
lines changed

5 files changed

+97
-43
lines changed

cache-tree.c

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#define USE_THE_REPOSITORY_VARIABLE
22

33
#include "git-compat-util.h"
4+
#include "gettext.h"
45
#include "hex.h"
56
#include "lockfile.h"
67
#include "tree.h"
@@ -865,15 +866,15 @@ int cache_tree_matches_traversal(struct cache_tree *root,
865866
return 0;
866867
}
867868

868-
static void verify_one_sparse(struct index_state *istate,
869-
struct strbuf *path,
870-
int pos)
869+
static int verify_one_sparse(struct index_state *istate,
870+
struct strbuf *path,
871+
int pos)
871872
{
872873
struct cache_entry *ce = istate->cache[pos];
873-
874874
if (!S_ISSPARSEDIR(ce->ce_mode))
875-
BUG("directory '%s' is present in index, but not sparse",
876-
path->buf);
875+
return error(_("directory '%s' is present in index, but not sparse"),
876+
path->buf);
877+
return 0;
877878
}
878879

879880
/*
@@ -882,6 +883,7 @@ static void verify_one_sparse(struct index_state *istate,
882883
* 1 - Restart verification - a call to ensure_full_index() freed the cache
883884
* tree that is being verified and verification needs to be restarted from
884885
* the new toplevel cache tree.
886+
* -1 - Verification failed.
885887
*/
886888
static int verify_one(struct repository *r,
887889
struct index_state *istate,
@@ -891,18 +893,23 @@ static int verify_one(struct repository *r,
891893
int i, pos, len = path->len;
892894
struct strbuf tree_buf = STRBUF_INIT;
893895
struct object_id new_oid;
896+
int ret;
894897

895898
for (i = 0; i < it->subtree_nr; i++) {
896899
strbuf_addf(path, "%s/", it->down[i]->name);
897-
if (verify_one(r, istate, it->down[i]->cache_tree, path))
898-
return 1;
900+
ret = verify_one(r, istate, it->down[i]->cache_tree, path);
901+
if (ret)
902+
goto out;
903+
899904
strbuf_setlen(path, len);
900905
}
901906

902907
if (it->entry_count < 0 ||
903908
/* no verification on tests (t7003) that replace trees */
904-
lookup_replace_object(r, &it->oid) != &it->oid)
905-
return 0;
909+
lookup_replace_object(r, &it->oid) != &it->oid) {
910+
ret = 0;
911+
goto out;
912+
}
906913

907914
if (path->len) {
908915
/*
@@ -912,19 +919,26 @@ static int verify_one(struct repository *r,
912919
*/
913920
int is_sparse = istate->sparse_index;
914921
pos = index_name_pos(istate, path->buf, path->len);
915-
if (is_sparse && !istate->sparse_index)
916-
return 1;
922+
if (is_sparse && !istate->sparse_index) {
923+
ret = 1;
924+
goto out;
925+
}
917926

918927
if (pos >= 0) {
919-
verify_one_sparse(istate, path, pos);
920-
return 0;
928+
ret = verify_one_sparse(istate, path, pos);
929+
goto out;
921930
}
922931

923932
pos = -pos - 1;
924933
} else {
925934
pos = 0;
926935
}
927936

937+
if (it->entry_count + pos > istate->cache_nr) {
938+
ret = error(_("corrupted cache-tree has entries not present in index"));
939+
goto out;
940+
}
941+
928942
i = 0;
929943
while (i < it->entry_count) {
930944
struct cache_entry *ce = istate->cache[pos + i];
@@ -935,16 +949,23 @@ static int verify_one(struct repository *r,
935949
unsigned mode;
936950
int entlen;
937951

938-
if (ce->ce_flags & (CE_STAGEMASK | CE_INTENT_TO_ADD | CE_REMOVE))
939-
BUG("%s with flags 0x%x should not be in cache-tree",
940-
ce->name, ce->ce_flags);
952+
if (ce->ce_flags & (CE_STAGEMASK | CE_INTENT_TO_ADD | CE_REMOVE)) {
953+
ret = error(_("%s with flags 0x%x should not be in cache-tree"),
954+
ce->name, ce->ce_flags);
955+
goto out;
956+
}
957+
941958
name = ce->name + path->len;
942959
slash = strchr(name, '/');
943960
if (slash) {
944961
entlen = slash - name;
962+
945963
sub = find_subtree(it, ce->name + path->len, entlen, 0);
946-
if (!sub || sub->cache_tree->entry_count < 0)
947-
BUG("bad subtree '%.*s'", entlen, name);
964+
if (!sub || sub->cache_tree->entry_count < 0) {
965+
ret = error(_("bad subtree '%.*s'"), entlen, name);
966+
goto out;
967+
}
968+
948969
oid = &sub->cache_tree->oid;
949970
mode = S_IFDIR;
950971
i += sub->cache_tree->entry_count;
@@ -957,27 +978,50 @@ static int verify_one(struct repository *r,
957978
strbuf_addf(&tree_buf, "%o %.*s%c", mode, entlen, name, '\0');
958979
strbuf_add(&tree_buf, oid->hash, r->hash_algo->rawsz);
959980
}
981+
960982
hash_object_file(r->hash_algo, tree_buf.buf, tree_buf.len, OBJ_TREE,
961983
&new_oid);
962-
if (!oideq(&new_oid, &it->oid))
963-
BUG("cache-tree for path %.*s does not match. "
964-
"Expected %s got %s", len, path->buf,
965-
oid_to_hex(&new_oid), oid_to_hex(&it->oid));
984+
985+
if (!oideq(&new_oid, &it->oid)) {
986+
ret = error(_("cache-tree for path %.*s does not match. "
987+
"Expected %s got %s"), len, path->buf,
988+
oid_to_hex(&new_oid), oid_to_hex(&it->oid));
989+
goto out;
990+
}
991+
992+
ret = 0;
993+
out:
966994
strbuf_setlen(path, len);
967995
strbuf_release(&tree_buf);
968-
return 0;
996+
return ret;
969997
}
970998

971-
void cache_tree_verify(struct repository *r, struct index_state *istate)
999+
int cache_tree_verify(struct repository *r, struct index_state *istate)
9721000
{
9731001
struct strbuf path = STRBUF_INIT;
1002+
int ret;
9741003

975-
if (!istate->cache_tree)
976-
return;
977-
if (verify_one(r, istate, istate->cache_tree, &path)) {
1004+
if (!istate->cache_tree) {
1005+
ret = 0;
1006+
goto out;
1007+
}
1008+
1009+
ret = verify_one(r, istate, istate->cache_tree, &path);
1010+
if (ret < 0)
1011+
goto out;
1012+
if (ret > 0) {
9781013
strbuf_reset(&path);
979-
if (verify_one(r, istate, istate->cache_tree, &path))
1014+
1015+
ret = verify_one(r, istate, istate->cache_tree, &path);
1016+
if (ret < 0)
1017+
goto out;
1018+
if (ret > 0)
9801019
BUG("ensure_full_index() called twice while verifying cache tree");
9811020
}
1021+
1022+
ret = 0;
1023+
1024+
out:
9821025
strbuf_release(&path);
1026+
return ret;
9831027
}

cache-tree.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
3333

3434
int cache_tree_fully_valid(struct cache_tree *);
3535
int cache_tree_update(struct index_state *, int);
36-
void cache_tree_verify(struct repository *, struct index_state *);
36+
int cache_tree_verify(struct repository *, struct index_state *);
3737

3838
/* bitmasks to write_index_as_tree flags */
3939
#define WRITE_TREE_MISSING_OK 1

read-cache.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3347,8 +3347,9 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
33473347
int new_shared_index, ret, test_split_index_env;
33483348
struct split_index *si = istate->split_index;
33493349

3350-
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
3351-
cache_tree_verify(the_repository, istate);
3350+
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0) &&
3351+
cache_tree_verify(the_repository, istate) < 0)
3352+
return -1;
33523353

33533354
if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) {
33543355
if (flags & COMMIT_LOCK)

t/t4058-diff-duplicates.sh

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# that the diff output isn't wildly unreasonable.
1111

1212
test_description='test tree diff when trees have duplicate entries'
13+
14+
TEST_PASSES_SANITIZE_LEAK=true
1315
. ./test-lib.sh
1416

1517
# make_tree_entry <mode> <mode> <sha1>
@@ -132,22 +134,23 @@ test_expect_success 'create a few commits' '
132134
rm commit_id up final
133135
'
134136

135-
test_expect_failure 'git read-tree does not segfault' '
136-
test_when_finished rm .git/index.lock &&
137-
test_might_fail git read-tree --reset base
137+
test_expect_success 'git read-tree does not segfault' '
138+
test_must_fail git read-tree --reset base 2>err &&
139+
test_grep "error: corrupted cache-tree has entries not present in index" err
138140
'
139141

140-
test_expect_failure 'reset --hard does not segfault' '
141-
test_when_finished rm .git/index.lock &&
142+
test_expect_success 'reset --hard does not segfault' '
142143
git checkout base &&
143-
test_might_fail git reset --hard
144+
test_must_fail git reset --hard 2>err &&
145+
test_grep "error: corrupted cache-tree has entries not present in index" err
144146
'
145147

146-
test_expect_failure 'git diff HEAD does not segfault' '
148+
test_expect_success 'git diff HEAD does not segfault' '
147149
git checkout base &&
148150
GIT_TEST_CHECK_CACHE_TREE=false &&
149151
git reset --hard &&
150-
test_might_fail git diff HEAD
152+
test_must_fail git diff HEAD 2>err &&
153+
test_grep "error: corrupted cache-tree has entries not present in index" err
151154
'
152155

153156
test_expect_failure 'can switch to another branch when status is empty' '

unpack-trees.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,8 @@ static int traverse_by_cache_tree(int pos, int nr_entries, int nr_names,
808808

809809
if (!o->merge)
810810
BUG("We need cache-tree to do this optimization");
811+
if (nr_entries + pos > o->src_index->cache_nr)
812+
return error(_("corrupted cache-tree has entries not present in index"));
811813

812814
/*
813815
* Do what unpack_callback() and unpack_single_entry() normally
@@ -2072,9 +2074,13 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
20722074
if (o->dst_index) {
20732075
move_index_extensions(&o->internal.result, o->src_index);
20742076
if (!ret) {
2075-
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
2076-
cache_tree_verify(the_repository,
2077-
&o->internal.result);
2077+
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0) &&
2078+
cache_tree_verify(the_repository,
2079+
&o->internal.result) < 0) {
2080+
ret = -1;
2081+
goto done;
2082+
}
2083+
20782084
if (!o->skip_cache_tree_update &&
20792085
!cache_tree_fully_valid(o->internal.result.cache_tree))
20802086
cache_tree_update(&o->internal.result,

0 commit comments

Comments
 (0)