Merge branch 'js/pull-rebase-type-shorthand'
[git] / t / t5313-pack-bounds-checks.sh
1 #!/bin/sh
2
3 test_description='bounds-checking of access to mmapped on-disk file formats'
4 . ./test-lib.sh
5
6 clear_base () {
7         test_when_finished 'restore_base' &&
8         rm -f $base
9 }
10
11 restore_base () {
12         cp base-backup/* .git/objects/pack/
13 }
14
15 do_pack () {
16         pack_objects=$1; shift
17         sha1=$(
18                 for i in $pack_objects
19                 do
20                         echo $i
21                 done | git pack-objects "$@" .git/objects/pack/pack
22         ) &&
23         pack=.git/objects/pack/pack-$sha1.pack &&
24         idx=.git/objects/pack/pack-$sha1.idx &&
25         chmod +w $pack $idx &&
26         test_when_finished 'rm -f "$pack" "$idx"'
27 }
28
29 munge () {
30         printf "$3" | dd of="$1" bs=1 conv=notrunc seek=$2
31 }
32
33 # Offset in a v2 .idx to its initial and extended offset tables. For an index
34 # with "nr" objects, this is:
35 #
36 #   magic(4) + version(4) + fan-out(4*256) + sha1s(20*nr) + crc(4*nr),
37 #
38 # for the initial, and another ofs(4*nr) past that for the extended.
39 #
40 ofs_table () {
41         echo $((4 + 4 + 4*256 + 20*$1 + 4*$1))
42 }
43 extended_table () {
44         echo $(($(ofs_table "$1") + 4*$1))
45 }
46
47 test_expect_success 'set up base packfile and variables' '
48         # the hash of this content starts with ff, which
49         # makes some later computations much simpler
50         echo 74 >file &&
51         git add file &&
52         git commit -m base &&
53         git repack -ad &&
54         base=$(echo .git/objects/pack/*) &&
55         chmod +w $base &&
56         mkdir base-backup &&
57         cp $base base-backup/ &&
58         object=$(git rev-parse HEAD:file)
59 '
60
61 test_expect_success 'pack/index object count mismatch' '
62         do_pack $object &&
63         munge $pack 8 "\377\0\0\0" &&
64         clear_base &&
65
66         # We enumerate the objects from the completely-fine
67         # .idx, but notice later that the .pack is bogus
68         # and fail to show any data.
69         echo "$object missing" >expect &&
70         git cat-file --batch-all-objects --batch-check >actual &&
71         test_cmp expect actual &&
72
73         # ...and here fail to load the object (without segfaulting),
74         # but fallback to a good copy if available.
75         test_must_fail git cat-file blob $object &&
76         restore_base &&
77         git cat-file blob $object >actual &&
78         test_cmp file actual &&
79
80         # ...and make sure that index-pack --verify, which has its
81         # own reading routines, does not segfault.
82         test_must_fail git index-pack --verify $pack
83 '
84
85 test_expect_success 'matched bogus object count' '
86         do_pack $object &&
87         munge $pack 8 "\377\0\0\0" &&
88         munge $idx $((255 * 4)) "\377\0\0\0" &&
89         clear_base &&
90
91         # Unlike above, we should notice early that the .idx is totally
92         # bogus, and not even enumerate its contents.
93         >expect &&
94         git cat-file --batch-all-objects --batch-check >actual &&
95         test_cmp expect actual &&
96
97         # But as before, we can do the same object-access checks.
98         test_must_fail git cat-file blob $object &&
99         restore_base &&
100         git cat-file blob $object >actual &&
101         test_cmp file actual &&
102
103         test_must_fail git index-pack --verify $pack
104 '
105
106 # Note that we cannot check the fallback case for these
107 # further .idx tests, as we notice the problem in functions
108 # whose interface doesn't allow an error return (like use_pack()),
109 # and thus we just die().
110 #
111 # There's also no point in doing enumeration tests, as
112 # we are munging offsets here, which are about looking up
113 # specific objects.
114
115 test_expect_success 'bogus object offset (v1)' '
116         do_pack $object --index-version=1 &&
117         munge $idx $((4 * 256)) "\377\0\0\0" &&
118         clear_base &&
119         test_must_fail git cat-file blob $object &&
120         test_must_fail git index-pack --verify $pack
121 '
122
123 test_expect_success 'bogus object offset (v2, no msb)' '
124         do_pack $object --index-version=2 &&
125         munge $idx $(ofs_table 1) "\0\377\0\0" &&
126         clear_base &&
127         test_must_fail git cat-file blob $object &&
128         test_must_fail git index-pack --verify $pack
129 '
130
131 test_expect_success 'bogus offset into v2 extended table' '
132         do_pack $object --index-version=2 &&
133         munge $idx $(ofs_table 1) "\377\0\0\0" &&
134         clear_base &&
135         test_must_fail git cat-file blob $object &&
136         test_must_fail git index-pack --verify $pack
137 '
138
139 test_expect_success 'bogus offset inside v2 extended table' '
140         # We need two objects here, so we can plausibly require
141         # an extended table (if the first object were larger than 2^31).
142         #
143         # Note that the value is important here. We want $object as
144         # the second entry in sorted-sha1 order. The sha1 of 1485 starts
145         # with "000", which sorts before that of $object (which starts
146         # with "fff").
147         second=$(echo 1485 | git hash-object -w --stdin) &&
148         do_pack "$object $second" --index-version=2 &&
149
150         # We have to make extra room for the table, so we cannot
151         # just munge in place as usual.
152         {
153                 dd if=$idx bs=1 count=$(($(ofs_table 2) + 4)) &&
154                 printf "\200\0\0\0" &&
155                 printf "\377\0\0\0\0\0\0\0" &&
156                 dd if=$idx bs=1 skip=$(extended_table 2)
157         } >tmp &&
158         mv tmp "$idx" &&
159         clear_base &&
160         test_must_fail git cat-file blob $object &&
161         test_must_fail git index-pack --verify $pack
162 '
163
164 test_expect_success 'bogus OFS_DELTA in packfile' '
165         # Generate a pack with a delta in it.
166         base=$(test-tool genrandom foo 3000 | git hash-object --stdin -w) &&
167         delta=$(test-tool genrandom foo 2000 | git hash-object --stdin -w) &&
168         do_pack "$base $delta" --delta-base-offset &&
169         rm -f .git/objects/??/* &&
170
171         # Double check that we have the delta we expect.
172         echo $base >expect &&
173         echo $delta | git cat-file --batch-check="%(deltabase)" >actual &&
174         test_cmp expect actual &&
175
176         # Now corrupt it. We assume the varint size for the delta is small
177         # enough to fit in the first byte (which it should be, since it
178         # is a pure deletion from the base), and that original ofs_delta
179         # takes 2 bytes (which it should, as it should be ~3000).
180         ofs=$(git show-index <$idx | grep $delta | cut -d" " -f1) &&
181         munge $pack $(($ofs + 1)) "\177\377" &&
182         test_must_fail git cat-file blob $delta >/dev/null
183 '
184
185 test_done