Commit message: index: fix out-of-bounds read with invalid index entry prefix length

The index format in version 4 has prefix-compressed entries, where every
index entry can compress its path by using a path prefix of the previous
entry. Since implmenting support for this index format version in commit
5625d86b9 (index: support index v4, 2016-05-17), though, we do not
correctly verify that the prefix length that we want to reuse is
actually smaller or equal to the amount of characters than the length of
the previous index entry's path. This can lead to a an integer underflow
and subsequently to an out-of-bounds read.

Fix this by verifying that the prefix is actually smaller than the
previous entry's path length.

Reported-by: Krishna Ram Prakash R <krp@gtux.in>
Reported-by: Vivek Parikh <viv0411.parikh@gmail.com>
CVE: CVE-2018-8098
CWE: CWE-190
Vulnerability description: Integer overflow in the index.c:read_entry() function while decompressing a compressed prefix length in libgit2 before v0.26.2 allows an attacker to cause a denial of service (out-of-bounds read) via a crafted repository index file.

Project: libgit2
File: index.c

static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
{
	int error = 0;
	unsigned int i;
	struct index_header header = { 0 };
	git_oid checksum_calculated, checksum_expected;
	const char *last = NULL;
	const char *empty = "";

#define seek_forward(_increase) { \
	if (_increase >= buffer_size) { \
		error = index_error_invalid("ran out of data while parsing"); \
		goto done; } \
	buffer += _increase; \
	buffer_size -= _increase;\
}

	if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE)
		return index_error_invalid("insufficient buffer space");

	/* Precalculate the SHA1 of the files's contents -- we'll match it to
	 * the provided SHA1 in the footer */
	git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE);

	/* Parse header */
	if ((error = read_header(&header, buffer)) < 0)
		return error;

	index->version = header.version;
	if (index->version >= INDEX_VERSION_NUMBER_COMP)
		last = empty;

	seek_forward(INDEX_HEADER_SIZE);

	assert(!index->entries.length);

	if (index->ignore_case)
		git_idxmap_icase_resize((khash_t(idxicase) *) index->entries_map, header.entry_count);
	else
		git_idxmap_resize(index->entries_map, header.entry_count);

	/* Parse all the entries */
	for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) {
		git_index_entry *entry = NULL;
		size_t entry_size;

		if ((error = read_entry(&entry, &entry_size, index, buffer, buffer_size, last)) < 0) {
			error = index_error_invalid("invalid entry");
			goto done;
		}

		if ((error = git_vector_insert(&index->entries, entry)) < 0) {
			index_entry_free(entry);
			goto done;
		}

		INSERT_IN_MAP(index, entry, &error);

		if (error < 0) {
			index_entry_free(entry);
			goto done;
		}
		error = 0;

		if (index->version >= INDEX_VERSION_NUMBER_COMP)
			last = entry->path;

		seek_forward(entry_size);
	}

	if (i != header.entry_count) {
		error = index_error_invalid("header entries changed while parsing");
		goto done;
	}

	/* There's still space for some extensions! */
	while (buffer_size > INDEX_FOOTER_SIZE) {
		size_t extension_size;

		extension_size = read_extension(index, buffer, buffer_size);

		/* see if we have read any bytes from the extension */
		if (extension_size == 0) {
			error = index_error_invalid("extension is truncated");
			goto done;
		}

		seek_forward(extension_size);
	}

	if (buffer_size != INDEX_FOOTER_SIZE) {
		error = index_error_invalid(
			"buffer size does not match index footer size");
		goto done;
	}

	/* 160-bit SHA-1 over the content of the index file before this checksum. */
	git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer);

	if (git_oid__cmp(&checksum_calculated, &checksum_expected) != 0) {
		error = index_error_invalid(
			"calculated checksum does not match expected");
		goto done;
	}

	git_oid_cpy(&index->checksum, &checksum_calculated);

#undef seek_forward

	/* Entries are stored case-sensitively on disk, so re-sort now if
	 * in-memory index is supposed to be case-insensitive
	 */
	git_vector_set_sorted(&index->entries, !index->ignore_case);
	git_vector_sort(&index->entries);

done:
	return error;
}