Skip to content

Commit 7fafec0

Browse files
committed
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 parent f647bbc commit 7fafec0

File tree

2 files changed

+22
-14
lines changed

2 files changed

+22
-14
lines changed

src/tree.c

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -356,22 +356,21 @@ static int tree_error(const char *str, const char *path)
356356
return -1;
357357
}
358358

359-
static int parse_mode(unsigned int *modep, const char *buffer, size_t buffer_len, const char **buffer_out)
359+
static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, const char **buffer_out)
360360
{
361-
const char *buffer_end = buffer + buffer_len;
362-
unsigned char c;
363-
unsigned int mode = 0;
361+
int32_t mode;
362+
int error;
364363

365-
if (*buffer == ' ')
364+
if (!buffer_len || git__isspace(*buffer))
366365
return -1;
367366

368-
while (buffer < buffer_end && (c = *buffer++) != ' ') {
369-
if (c < '0' || c > '7')
370-
return -1;
371-
mode = (mode << 3) + (c - '0');
372-
}
373-
*modep = mode;
374-
*buffer_out = buffer;
367+
if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0)
368+
return error;
369+
370+
if (mode < 0 || mode > UINT16_MAX)
371+
return -1;
372+
373+
*mode_out = mode;
375374

376375
return 0;
377376
}
@@ -393,11 +392,14 @@ int git_tree__parse_raw(void *_tree, const char *data, size_t size)
393392
git_tree_entry *entry;
394393
size_t filename_len;
395394
const char *nul;
396-
unsigned int attr;
395+
uint16_t attr;
397396

398397
if (parse_mode(&attr, buffer, buffer_end - buffer, &buffer) < 0 || !buffer)
399398
return tree_error("failed to parse tree: can't parse filemode", NULL);
400399

400+
if (buffer >= buffer_end || (*buffer++) != ' ')
401+
return tree_error("failed to parse tree: missing space after filemode", NULL);
402+
401403
if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL)
402404
return tree_error("failed to parse tree: object is corrupted", NULL);
403405

tests/object/tree/parse.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,13 @@ void test_object_tree_parse__mode_doesnt_cause_oob_read(void)
118118
* later fail to parse the OID with a different error
119119
* message
120120
*/
121-
cl_assert(strstr(giterr_last()->message, "object is corrupted"));
121+
cl_assert_equal_s(giterr_last()->message, "failed to parse tree: missing space after filemode");
122+
}
123+
124+
void test_object_tree_parse__unreasonably_large_mode_fails(void)
125+
{
126+
const char data[] = "10000000000000000000000000 bar\x00" OID1_HEX;
127+
assert_tree_fails(data, ARRAY_SIZE(data) - 1);
122128
}
123129

124130
void test_object_tree_parse__missing_filename_separator_fails(void)

0 commit comments

Comments
 (0)