aboutsummaryrefslogtreecommitdiff
path: root/t/t5313-pack-bounds-checks.sh
blob: a8a587abc3799f069deab3bc48914cea81bb15cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/bin/sh

test_description='bounds-checking of access to mmapped on-disk file formats'
. ./test-lib.sh

clear_base () {
	test_when_finished 'restore_base' &&
	rm -f $base
}

restore_base () {
	cp base-backup/* .git/objects/pack/
}

do_pack () {
	pack_objects=$1; shift
	sha1=$(
		for i in $pack_objects
		do
			echo $i
		done | git pack-objects "$@" .git/objects/pack/pack
	) &&
	pack=.git/objects/pack/pack-$sha1.pack &&
	idx=.git/objects/pack/pack-$sha1.idx &&
	chmod +w $pack $idx &&
	test_when_finished 'rm -f "$pack" "$idx"'
}

munge () {
	printf "$3" | dd of="$1" bs=1 conv=notrunc seek=$2
}

# Offset in a v2 .idx to its initial and extended offset tables. For an index
# with "nr" objects, this is:
#
#   magic(4) + version(4) + fan-out(4*256) + sha1s(20*nr) + crc(4*nr),
#
# for the initial, and another ofs(4*nr) past that for the extended.
#
ofs_table () {
	echo $((4 + 4 + 4*256 + 20*$1 + 4*$1))
}
extended_table () {
	echo $(($(ofs_table "$1") + 4*$1))
}

test_expect_success 'set up base packfile and variables' '
	# the hash of this content starts with ff, which
	# makes some later computations much simpler
	echo 74 >file &&
	git add file &&
	git commit -m base &&
	git repack -ad &&
	base=$(echo .git/objects/pack/*) &&
	chmod +w $base &&
	mkdir base-backup &&
	cp $base base-backup/ &&
	object=$(git rev-parse HEAD:file)
'

test_expect_success 'pack/index object count mismatch' '
	do_pack $object &&
	munge $pack 8 "\377\0\0\0" &&
	clear_base &&

	# We enumerate the objects from the completely-fine
	# .idx, but notice later that the .pack is bogus
	# and fail to show any data.
	echo "$object missing" >expect &&
	git cat-file --batch-all-objects --batch-check >actual &&
	test_cmp expect actual &&

	# ...and here fail to load the object (without segfaulting),
	# but fallback to a good copy if available.
	test_must_fail git cat-file blob $object &&
	restore_base &&
	git cat-file blob $object >actual &&
	test_cmp file actual &&

	# ...and make sure that index-pack --verify, which has its
	# own reading routines, does not segfault.
	test_must_fail git index-pack --verify $pack
'

test_expect_success 'matched bogus object count' '
	do_pack $object &&
	munge $pack 8 "\377\0\0\0" &&
	munge $idx $((255 * 4)) "\377\0\0\0" &&
	clear_base &&

	# Unlike above, we should notice early that the .idx is totally
	# bogus, and not even enumerate its contents.
	>expect &&
	git cat-file --batch-all-objects --batch-check >actual &&
	test_cmp expect actual &&

	# But as before, we can do the same object-access checks.
	test_must_fail git cat-file blob $object &&
	restore_base &&
	git cat-file blob $object >actual &&
	test_cmp file actual &&

	test_must_fail git index-pack --verify $pack
'

# Note that we cannot check the fallback case for these
# further .idx tests, as we notice the problem in functions
# whose interface doesn't allow an error return (like use_pack()),
# and thus we just die().
#
# There's also no point in doing enumeration tests, as
# we are munging offsets here, which are about looking up
# specific objects.

test_expect_success 'bogus object offset (v1)' '
	do_pack $object --index-version=1 &&
	munge $idx $((4 * 256)) "\377\0\0\0" &&
	clear_base &&
	test_must_fail git cat-file blob $object &&
	test_must_fail git index-pack --verify $pack
'

test_expect_success 'bogus object offset (v2, no msb)' '
	do_pack $object --index-version=2 &&
	munge $idx $(ofs_table 1) "\0\377\0\0" &&
	clear_base &&
	test_must_fail git cat-file blob $object &&
	test_must_fail git index-pack --verify $pack
'

test_expect_success 'bogus offset into v2 extended table' '
	do_pack $object --index-version=2 &&
	munge $idx $(ofs_table 1) "\377\0\0\0" &&
	clear_base &&
	test_must_fail git cat-file blob $object &&
	test_must_fail git index-pack --verify $pack
'

test_expect_success 'bogus offset inside v2 extended table' '
	# We need two objects here, so we can plausibly require
	# an extended table (if the first object were larger than 2^31).
	do_pack "$object $(git rev-parse HEAD)" --index-version=2 &&

	# We have to make extra room for the table, so we cannot
	# just munge in place as usual.
	{
		dd if=$idx bs=1 count=$(($(ofs_table 2) + 4)) &&
		printf "\200\0\0\0" &&
		printf "\377\0\0\0\0\0\0\0" &&
		dd if=$idx bs=1 skip=$(extended_table 2)
	} >tmp &&
	mv tmp "$idx" &&
	clear_base &&
	test_must_fail git cat-file blob $object &&
	test_must_fail git index-pack --verify $pack
'

test_expect_success 'bogus OFS_DELTA in packfile' '
	# Generate a pack with a delta in it.
	base=$(test-genrandom foo 3000 | git hash-object --stdin -w) &&
	delta=$(test-genrandom foo 2000 | git hash-object --stdin -w) &&
	do_pack "$base $delta" --delta-base-offset &&
	rm -f .git/objects/??/* &&

	# Double check that we have the delta we expect.
	echo $base >expect &&
	echo $delta | git cat-file --batch-check="%(deltabase)" >actual &&
	test_cmp expect actual &&

	# Now corrupt it. We assume the varint size for the delta is small
	# enough to fit in the first byte (which it should be, since it
	# is a pure deletion from the base), and that original ofs_delta
	# takes 2 bytes (which it should, as it should be ~3000).
	ofs=$(git show-index <$idx | grep $delta | cut -d" " -f1) &&
	munge $pack $(($ofs + 1)) "\177\377" &&
	test_must_fail git cat-file blob $delta >/dev/null
'

test_done