t: introduce tests for unexpected object types

Call an object's type "unexpected" when the actual type of an object does not match Git's contextual expectation. For example, a tree entry whose mode differs from the object's actual type, or a commit's parent which is not another commit, and so on. This can manifest itself in various unfortunate ways, including Git SIGSEGV-ing under specific conditions. Consider the following example: Git traverses a blob (say, via `git rev-list`), and then tries to read out a tree-entry which lists that object as something other than a blob. In this case, `lookup_blob()` will return NULL, and the subsequent dereference will result in a SIGSEGV. Introduce tests that present objects of "unexpected" type in the above fashion to 'git rev-list'. Mark as failures the combinations that are already broken (i.e., they exhibit the segfault described above). In the cases that are not broken (i.e., they have NULL-ness checks or similar), mark these as expecting success. We might hit an unexpected type in two different ways (imagine we have a tree entry that claims to be a tree but actually points to a blob): - when we call lookup_tree(), we might find that we've already seen the object referenced as a blob, in which case we'd get NULL. We can exercise this with "git rev-list --objects $blob $tree", which guarantees that the blob will have been parsed before we look in the tree. These tests are marked as "seen" in the test script. - we call lookup_tree() successfully, but when we try to read the object, we find out it's something else. We construct our tests such that $blob is not otherwise mentioned in $tree. These tests are marked as "lone" in the script. We should check that we behave sensibly in both cases (especially because it is easy for a malicious actor to provoke one case or the other). Co-authored-by: Jeff King <peff@peff.net> Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Apr 9, 2019 at 19:13 UTC 0616617c7e1470e11c10dcb0fb72100ce3b15ec4
1 file changed +123
t/t6102-rev-list-unexpected-objects.sh new
+123
@@ -0,0 +1,123 @@
1 +#!/bin/sh
2 +
3 +test_description='git rev-list should handle unexpected object types'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup well-formed objects' '
8 + blob="$(printf "foo" | git hash-object -w --stdin)" &&
9 + tree="$(printf "100644 blob $blob\tfoo" | git mktree)" &&
10 + commit="$(git commit-tree $tree -m "first commit")" &&
11 + git cat-file commit $commit >good-commit
12 +'
13 +
14 +test_expect_success 'setup unexpected non-blob entry' '
15 + printf "100644 foo\0$(echo $tree | hex2oct)" >broken-tree &&
16 + broken_tree="$(git hash-object -w --literally -t tree broken-tree)"
17 +'
18 +
19 +test_expect_failure 'traverse unexpected non-blob entry (lone)' '
20 + test_must_fail git rev-list --objects $broken_tree
21 +'
22 +
23 +test_expect_failure 'traverse unexpected non-blob entry (seen)' '
24 + test_must_fail git rev-list --objects $tree $broken_tree
25 +'
26 +
27 +test_expect_success 'setup unexpected non-tree entry' '
28 + printf "40000 foo\0$(echo $blob | hex2oct)" >broken-tree &&
29 + broken_tree="$(git hash-object -w --literally -t tree broken-tree)"
30 +'
31 +
32 +test_expect_failure 'traverse unexpected non-tree entry (lone)' '
33 + test_must_fail git rev-list --objects $broken_tree
34 +'
35 +
36 +test_expect_failure 'traverse unexpected non-tree entry (seen)' '
37 + test_must_fail git rev-list --objects $blob $broken_tree
38 +'
39 +
40 +test_expect_success 'setup unexpected non-commit parent' '
41 + sed "/^author/ { h; s/.*/parent $blob/; G; }" <good-commit \
42 + >broken-commit &&
43 + broken_commit="$(git hash-object -w --literally -t commit \
44 + broken-commit)"
45 +'
46 +
47 +test_expect_success 'traverse unexpected non-commit parent (lone)' '
48 + test_must_fail git rev-list --objects $broken_commit >output 2>&1 &&
49 + test_i18ngrep "not a commit" output
50 +'
51 +
52 +test_expect_success 'traverse unexpected non-commit parent (seen)' '
53 + test_must_fail git rev-list --objects $commit $broken_commit \
54 + >output 2>&1 &&
55 + test_i18ngrep "not a commit" output
56 +'
57 +
58 +test_expect_success 'setup unexpected non-tree root' '
59 + sed -e "s/$tree/$blob/" <good-commit >broken-commit &&
60 + broken_commit="$(git hash-object -w --literally -t commit \
61 + broken-commit)"
62 +'
63 +
64 +test_expect_failure 'traverse unexpected non-tree root (lone)' '
65 + test_must_fail git rev-list --objects $broken_commit
66 +'
67 +
68 +test_expect_failure 'traverse unexpected non-tree root (seen)' '
69 + test_must_fail git rev-list --objects $blob $broken_commit
70 +'
71 +
72 +test_expect_success 'setup unexpected non-commit tag' '
73 + git tag -a -m "tagged commit" tag $commit &&
74 + git cat-file tag tag >good-tag &&
75 + test_when_finished "git tag -d tag" &&
76 + sed -e "s/$commit/$blob/" <good-tag >broken-tag &&
77 + tag=$(git hash-object -w --literally -t tag broken-tag)
78 +'
79 +
80 +test_expect_success 'traverse unexpected non-commit tag (lone)' '
81 + test_must_fail git rev-list --objects $tag
82 +'
83 +
84 +test_expect_success 'traverse unexpected non-commit tag (seen)' '
85 + test_must_fail git rev-list --objects $blob $tag >output 2>&1 &&
86 + test_i18ngrep "not a commit" output
87 +'
88 +
89 +test_expect_success 'setup unexpected non-tree tag' '
90 + git tag -a -m "tagged tree" tag $tree &&
91 + git cat-file tag tag >good-tag &&
92 + test_when_finished "git tag -d tag" &&
93 + sed -e "s/$tree/$blob/" <good-tag >broken-tag &&
94 + tag=$(git hash-object -w --literally -t tag broken-tag)
95 +'
96 +
97 +test_expect_success 'traverse unexpected non-tree tag (lone)' '
98 + test_must_fail git rev-list --objects $tag
99 +'
100 +
101 +test_expect_success 'traverse unexpected non-tree tag (seen)' '
102 + test_must_fail git rev-list --objects $blob $tag >output 2>&1 &&
103 + test_i18ngrep "not a tree" output
104 +'
105 +
106 +test_expect_success 'setup unexpected non-blob tag' '
107 + git tag -a -m "tagged blob" tag $blob &&
108 + git cat-file tag tag >good-tag &&
109 + test_when_finished "git tag -d tag" &&
110 + sed -e "s/$blob/$commit/" <good-tag >broken-tag &&
111 + tag=$(git hash-object -w --literally -t tag broken-tag)
112 +'
113 +
114 +test_expect_failure 'traverse unexpected non-blob tag (lone)' '
115 + test_must_fail git rev-list --objects $tag
116 +'
117 +
118 +test_expect_success 'traverse unexpected non-blob tag (seen)' '
119 + test_must_fail git rev-list --objects $commit $tag >output 2>&1 &&
120 + test_i18ngrep "not a blob" output
121 +'
122 +
123 +test_done