Hash :
7fafec0e
Author :
Date :
2018-10-29T18:32:39
tree: fix integer overflow when reading unreasonably large filemodes The `parse_mode` option uses an open-coded octal number parser. The parser is quite naive in that it simply parses until hitting a character that is not in the accepted range of '0' - '7', completely ignoring the fact that we can at most accept a 16 bit unsigned integer as filemode. If the filemode is bigger than UINT16_MAX, it will thus overflow and provide an invalid filemode for the object entry. Fix the issue by using `git__strntol32` instead and doing a bounds check. As this function already handles overflows, it neatly solves the problem. Note that previously, `parse_mode` was also skipping the character immediately after the filemode. In proper trees, this should be a simple space, but in fact the parser accepted any character and simply skipped over it. As a consequence of using `git__strntol32`, we now need to an explicit check for a trailing whitespace after having parsed the filemode. Because of the newly introduced error message, the test object::tree::parse::mode_doesnt_cause_oob_read needs adjustment to its error message check, which in fact is a good thing as it demonstrates that we now fail looking for the whitespace immediately following the filemode. Add a test that shows that we will fail to parse such invalid filemodes now.
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
#include "clar_libgit2.h"
#include "tree.h"
#include "object.h"
#define OID1_HEX \
"\xae\x90\xf1\x2e\xea\x69\x97\x29\xed\x24" \
"\x55\x5e\x40\xb9\xfd\x66\x9d\xa1\x2a\x12"
#define OID1_STR "ae90f12eea699729ed24555e40b9fd669da12a12"
#define OID2_HEX \
"\xe8\xbf\xe5\xaf\x39\x57\x9a\x7e\x48\x98" \
"\xbb\x23\xf3\xa7\x6a\x72\xc3\x68\xce\xe6"
#define OID2_STR "e8bfe5af39579a7e4898bb23f3a76a72c368cee6"
typedef struct {
const char *filename;
uint16_t attr;
const char *oid;
} expected_entry;
static void assert_tree_parses(const char *data, size_t datalen,
expected_entry *expected_entries, size_t expected_nentries)
{
git_tree *tree;
size_t n;
if (!datalen)
datalen = strlen(data);
cl_git_pass(git_object__from_raw((git_object **) &tree, data, datalen, GIT_OBJ_TREE));
cl_assert_equal_i(git_tree_entrycount(tree), expected_nentries);
for (n = 0; n < expected_nentries; n++) {
expected_entry *expected = expected_entries + n;
const git_tree_entry *entry;
git_oid oid;
cl_git_pass(git_oid_fromstr(&oid, expected->oid));
cl_assert(entry = git_tree_entry_byname(tree, expected->filename));
cl_assert_equal_s(expected->filename, entry->filename);
cl_assert_equal_i(expected->attr, entry->attr);
cl_assert_equal_oid(&oid, entry->oid);
}
git_object_free(&tree->object);
}
static void assert_tree_fails(const char *data, size_t datalen)
{
git_object *object;
if (!datalen)
datalen = strlen(data);
cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJ_TREE));
}
void test_object_tree_parse__single_blob_parses(void)
{
expected_entry entries[] = {
{ "foo", 0100644, OID1_STR },
};
const char data[] = "100644 foo\x00" OID1_HEX;
assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries));
}
void test_object_tree_parse__single_tree_parses(void)
{
expected_entry entries[] = {
{ "foo", 040000, OID1_STR },
};
const char data[] = "040000 foo\x00" OID1_HEX;
assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries));
}
void test_object_tree_parse__leading_filename_spaces_parse(void)
{
expected_entry entries[] = {
{ " bar", 0100644, OID1_STR },
};
const char data[] = "100644 bar\x00" OID1_HEX;
assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries));
}
void test_object_tree_parse__multiple_entries_parse(void)
{
expected_entry entries[] = {
{ "bar", 0100644, OID1_STR },
{ "foo", 040000, OID2_STR },
};
const char data[] =
"100644 bar\x00" OID1_HEX
"040000 foo\x00" OID2_HEX;
assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries));
}
void test_object_tree_parse__invalid_mode_fails(void)
{
const char data[] = "10x644 bar\x00" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__missing_mode_fails(void)
{
const char data[] = " bar\x00" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__mode_doesnt_cause_oob_read(void)
{
const char data[] = "100644 bar\x00" OID1_HEX;
assert_tree_fails(data, 2);
/*
* An oob-read would correctly parse the filename and
* later fail to parse the OID with a different error
* message
*/
cl_assert_equal_s(giterr_last()->message, "failed to parse tree: missing space after filemode");
}
void test_object_tree_parse__unreasonably_large_mode_fails(void)
{
const char data[] = "10000000000000000000000000 bar\x00" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__missing_filename_separator_fails(void)
{
const char data[] = "100644bar\x00" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__missing_filename_terminator_fails(void)
{
const char data[] = "100644 bar" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__empty_filename_fails(void)
{
const char data[] = "100644 \x00" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__trailing_garbage_fails(void)
{
const char data[] = "100644 bar\x00" OID1_HEX "x";
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__leading_space_fails(void)
{
const char data[] = " 100644 bar\x00" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
}
void test_object_tree_parse__truncated_oid_fails(void)
{
const char data[] = " 100644 bar\x00" OID1_HEX;
assert_tree_fails(data, ARRAY_SIZE(data) - 2);
}