Commit message: Fix integer underflow vulnerability in L3 decode.

Marcin 'Icewall' Noga of Cisco TALOS discovered that the level 3 header
decoding routines were vulnerable to an integer underflow, if the 32-bit
header length was less than the base level 3 header length. This could
lead to an exploitable heap corruption condition.

Thanks go to Marcin Noga and Regina Wilson of Cisco TALOS for reporting
this vulnerability.
CVE: CVE-2016-2347
CWE: CWE-190
Vulnerability description: Integer underflow in the decode_level3_header function in lib/lha_file_header.c in Lhasa before 0.3.1 allows remote attackers to execute arbitrary code via a crafted archive.

Project: lhasa

static int check_common_crc(LHAFileHeader *header)
{
	uint16_t crc;

	crc = 0;
	lha_crc16_buf(&crc, header->raw_data, header->raw_data_len);

	return crc == header->common_crc;
}

Project: lhasa

static int check_l0_checksum(uint8_t *header, size_t header_len, size_t csum)
{
	unsigned int result;
	unsigned int i;

	result = 0;

	for (i = 0; i < header_len; ++i) {
		result += header[i];
	}

	return (result & 0xff) == csum;
}

Project: lhasa

static void collapse_path(char *filename)
{
	unsigned int currpath_len;
	char *currpath;
	char *r, *w;


	if (filename[0] == '/') {
		++filename;
	}


	currpath = filename;
	w = filename;

	for (r = filename; *r != '\0'; ++r) {
		*w++ = *r;


		if (*r == '/') {

			currpath_len = w - currpath - 1;


			if (currpath_len == 0
			 || (currpath_len == 1 && currpath[0] == '.')) {
				w = currpath;


			} else if (currpath_len == 2
			        && currpath[0] == '.' && currpath[1] == '.') {


				if (currpath == filename) {
					w = filename;
				} else {
					w = currpath - 1;

					while (w > filename) {
						if (*(w - 1) == '/') {
							break;
						}
						--w;
					}

					currpath = w;
				}


			} else {
				currpath = w;
			}
		}
	}

	*w = '\0';
}

Project: lhasa

static int decode_extended_headers(LHAFileHeader **header,
                                   unsigned int offset)
{
	unsigned int field_size;
	uint8_t *ext_header;
	size_t ext_header_len;
	size_t available_length;


	if ((*header)->header_level == 3) {
		field_size = 4;
	} else {
		field_size = 2;
	}

	available_length = RAW_DATA_LEN(header) - offset - field_size;

	while (offset <= RAW_DATA_LEN(header) - field_size) {
		ext_header = &RAW_DATA(header, offset + field_size);

		if (field_size == 4) {
			ext_header_len
			    = lha_decode_uint32(&RAW_DATA(header, offset));
		} else {
			ext_header_len
			    = lha_decode_uint16(&RAW_DATA(header, offset));
		}


		if (ext_header_len == 0) {
			break;
		} else if (ext_header_len < field_size + 1
		        || ext_header_len > available_length) {
			return 0;
		}


		lha_ext_header_decode(*header, ext_header[0], ext_header + 1,
		                      ext_header_len - field_size - 1);


		offset += ext_header_len;
		available_length -= ext_header_len;
	}

	return 1;
}

Project: lhasa

static unsigned int decode_ftime(uint8_t *buf)
{
	int raw;
	struct tm datetime;

	raw = (int) lha_decode_uint32(buf);

	if (raw == 0) {
		return 0;
	}


	memset(&datetime, 0, sizeof(struct tm));

	datetime.tm_sec = (raw << 1) & 0x3e;
	datetime.tm_min = (raw >> 5) & 0x3f;
	datetime.tm_hour = (raw >> 11) & 0x1f;
	datetime.tm_mday = (raw >> 16) & 0x1f;
	datetime.tm_mon = ((raw >> 21) & 0xf) - 1;
	datetime.tm_year = 80 + ((raw >> 25) & 0x7f);
	datetime.tm_wday = 0;
	datetime.tm_yday = 0;
	datetime.tm_isdst = -1;

	return (unsigned int) mktime(&datetime);
}

Project: lhasa

static int decode_level0_header(LHAFileHeader **header, LHAInputStream *stream)
{
	uint8_t header_len;
	uint8_t header_csum;
	size_t path_len;
	size_t min_len;

	header_len = RAW_DATA(header, 0);
	header_csum = RAW_DATA(header, 1);


	switch ((*header)->header_level) {
		case 0:
			min_len = LEVEL_0_MIN_HEADER_LEN;
			break;
		case 1:
			min_len = LEVEL_1_MIN_HEADER_LEN;
			break;

		default:
			return 0;
	}

	if (header_len < min_len) {
		return 0;
	}


	if (!extend_raw_data(header, stream,
	                     header_len + 2 - RAW_DATA_LEN(header))) {
		return 0;
	}


	if (!check_l0_checksum(&RAW_DATA(header, 2),
	                       RAW_DATA_LEN(header) - 2,
	                       header_csum)) {
		return 0;
	}


	memcpy((*header)->compress_method, &RAW_DATA(header, 2), 5);
	(*header)->compress_method[5] = '\0';


	(*header)->compressed_length = lha_decode_uint32(&RAW_DATA(header, 7));
	(*header)->length = lha_decode_uint32(&RAW_DATA(header, 11));


	(*header)->timestamp = decode_ftime(&RAW_DATA(header, 15));


	path_len = RAW_DATA(header, 21);

	if (min_len + path_len > header_len) {
		return 0;
	}


	if ((*header)->header_level == 0) {
		(*header)->os_type = LHA_OS_TYPE_UNKNOWN;
	} else {
		(*header)->os_type = RAW_DATA(header, 24 + path_len);
	}


	if (!process_level0_path(*header, &RAW_DATA(header, 22), path_len)) {
		return 0;
	}


	(*header)->crc = lha_decode_uint16(&RAW_DATA(header, 22 + path_len));


	if ((*header)->header_level == 0
	 && header_len > LEVEL_0_MIN_HEADER_LEN + path_len) {
		process_level0_extended_area(*header,
		  &RAW_DATA(header, LEVEL_0_MIN_HEADER_LEN + 2 + path_len),
		  header_len - LEVEL_0_MIN_HEADER_LEN - path_len);
	}

	return 1;
}

Project: lhasa

static int decode_level1_header(LHAFileHeader **header, LHAInputStream *stream)
{
	unsigned int ext_header_start;

	if (!decode_level0_header(header, stream)) {
		return 0;
	}


	ext_header_start = RAW_DATA_LEN(header) - 2;

	if (!read_l1_extended_headers(header, stream)
	 || !decode_extended_headers(header, ext_header_start)) {
		return 0;
	}

	return 1;
}

Project: lhasa

static int decode_level2_header(LHAFileHeader **header, LHAInputStream *stream)
{
	unsigned int header_len;

	header_len = lha_decode_uint16(&RAW_DATA(header, 0));

	if (header_len < LEVEL_2_HEADER_LEN) {
		return 0;
	}


	if (!extend_raw_data(header, stream,
	                     header_len - RAW_DATA_LEN(header))) {
		return 0;
	}


	memcpy((*header)->compress_method, &RAW_DATA(header, 2), 5);
	(*header)->compress_method[5] = '\0';


	(*header)->compressed_length = lha_decode_uint32(&RAW_DATA(header, 7));
	(*header)->length = lha_decode_uint32(&RAW_DATA(header, 11));


	(*header)->timestamp = lha_decode_uint32(&RAW_DATA(header, 15));


	(*header)->crc = lha_decode_uint16(&RAW_DATA(header, 21));


	(*header)->os_type = RAW_DATA(header, 23);


	if ((*header)->os_type == LHA_OS_TYPE_OS9_68K) {
		if (!extend_raw_data(header, stream, 2)) {
			return 0;
		}
	}

	if (!decode_extended_headers(header, 24)) {
		return 0;
	}

	return 1;
}

Project: lhasa

static void fix_msdos_allcaps(LHAFileHeader *header)
{
	unsigned int i;
	int is_allcaps;


	is_allcaps = 1;

	if (header->path != NULL) {
		for (i = 0; header->path[i] != '\0'; ++i) {
			if (islower((unsigned) header->path[i])) {
				is_allcaps = 0;
				break;
			}
		}
	}

	if (is_allcaps && header->filename != NULL) {
		for (i = 0; header->filename[i] != '\0'; ++i) {
			if (islower((unsigned) header->filename[i])) {
				is_allcaps = 0;
				break;
			}
		}
	}


	if (is_allcaps) {
		if (header->path != NULL) {
			for (i = 0; header->path[i] != '\0'; ++i) {
				header->path[i]
				    = tolower((unsigned) header->path[i]);
			}
		}
		if (header->filename != NULL) {
			for (i = 0; header->filename[i] != '\0'; ++i) {
				header->filename[i]
				    = tolower((unsigned) header->filename[i]);
			}
		}
	}
}

Project: lhasa

void lha_file_header_add_ref(LHAFileHeader *header)
{
	++header->_refcount;
}

Project: lhasa

void lha_file_header_free(LHAFileHeader *header)
{

	if (header->_refcount == 0) {
		return;
	}


	--header->_refcount;

	if (header->_refcount > 0) {
		return;
	}

	free(header->filename);
	free(header->path);
	free(header->symlink_target);
	free(header->unix_username);
	free(header->unix_group);
	free(header);
}

Project: lhasa

char *lha_file_header_full_path(LHAFileHeader *header)
{
	char *path;
	char *filename;
	char *result;

	if (header->path != NULL) {
		path = header->path;
	} else {
		path = "";
	}

	if (header->filename != NULL) {
		filename = header->filename;
	} else {
		filename = "";
	}

	result = malloc(strlen(path) + strlen(filename) + 1);

	if (result == NULL) {
		return NULL;
	}

	sprintf(result, "%s%s", path, filename);

	return result;
}

Project: lhasa

LHAFileHeader *lha_file_header_read(LHAInputStream *stream)
{
	LHAFileHeader *header;
	int success;



	header = calloc(1, sizeof(LHAFileHeader) + COMMON_HEADER_LEN);

	if (header == NULL) {
		return NULL;
	}

	memset(header, 0, sizeof(LHAFileHeader));

	header->_refcount = 1;


	header->raw_data = (uint8_t *) (header + 1);
	header->raw_data_len = COMMON_HEADER_LEN;

	if (!lha_input_stream_read(stream, header->raw_data,
	                           header->raw_data_len)) {
		goto fail;
	}


	header->header_level = header->raw_data[20];

	switch (header->header_level) {
		case 0:
			success = decode_level0_header(&header, stream);
			break;

		case 1:
			success = decode_level1_header(&header, stream);
			break;

		case 2:
			success = decode_level2_header(&header, stream);
			break;

		case 3:
			success = decode_level3_header(&header, stream);
			break;

		default:
			success = 0;
			break;
	}

	if (!success) {
		goto fail;
	}


	if (strcmp(header->compress_method, LHA_COMPRESS_TYPE_DIR) != 0) {
		if (header->filename == NULL) {
			goto fail;
		}
	} else if (!strcmp(header->compress_method, LHA_COMPRESS_TYPE_DIR)
	        && LHA_FILE_HAVE_EXTRA(header, LHA_FILE_UNIX_PERMS)
		&& (header->path != NULL || header->filename != NULL)
		&& (header->unix_perms & 0170000) == 0120000) {

		if (!parse_symlink(header)) {
			goto fail;
		}

	} else {
		if (header->path == NULL) {
			goto fail;
		}
	}


	if (header->os_type == LHA_OS_TYPE_UNKNOWN
	 || header->os_type == LHA_OS_TYPE_MSDOS
	 || header->os_type == LHA_OS_TYPE_ATARI
	 || header->os_type == LHA_OS_TYPE_OS2) {
		fix_msdos_allcaps(header);
	}


	if (header->path != NULL) {
		collapse_path(header->path);
	}


	if (header->os_type == LHA_OS_TYPE_OS9_68K
	 && LHA_FILE_HAVE_EXTRA(header, LHA_FILE_UNIX_PERMS)) {
		header->os9_perms = header->unix_perms;
		header->extra_flags |= LHA_FILE_OS9_PERMS;
	}


	if (LHA_FILE_HAVE_EXTRA(header, LHA_FILE_OS9_PERMS)) {
		os9_to_unix_permissions(header);
	}


	if (LHA_FILE_HAVE_EXTRA(header, LHA_FILE_COMMON_CRC)
	 && !check_common_crc(header)) {
		goto fail;
	}

	return header;
fail:
	lha_file_header_free(header);
	return NULL;
}

Project: lhasa

static void os9_to_unix_permissions(LHAFileHeader *header)
{
	unsigned int or, ow, oe, pr, pw, pe, d;


	or = (header->os9_perms & 0x01) != 0;
	ow = (header->os9_perms & 0x02) != 0;
	oe = (header->os9_perms & 0x04) != 0;
	pr = (header->os9_perms & 0x08) != 0;
	pw = (header->os9_perms & 0x10) != 0;
	pe = (header->os9_perms & 0x20) != 0;
	d = (header->os9_perms & 0x80) != 0;

	header->extra_flags |= LHA_FILE_UNIX_PERMS;
	header->unix_perms = (d << 14)
	                   | (or << 8) | (ow << 7) | (oe << 6)  // owner
	                   | (pr << 5) | (pw << 4) | (pe << 3)  // group
	                   | (pr << 2) | (pw << 1) | (pe << 0); // everyone
}

Project: lhasa

static int parse_symlink(LHAFileHeader *header)
{
	char *fullpath;
	char *p;


	fullpath = lha_file_header_full_path(header);

	if (fullpath == NULL) {
		return 0;
	}

	p = strchr(fullpath, '|');

	if (p == NULL) {
		free(fullpath);
		return 0;
	}

	header->symlink_target = strdup(p + 1);

	if (header->symlink_target == NULL) {
		free(fullpath);
		return 0;
	}


	*p = '\0';

	free(header->path);
	free(header->filename);
	header->path = NULL;
	header->filename = fullpath;


	return split_header_filename(header);
}

Project: lhasa

static void process_level0_extended_area(LHAFileHeader *header,
                                         uint8_t *data, size_t data_len)
{

	if (!strncmp(header->compress_method, "-pm", 3)) {
		return;
	}


	switch (data[0]) {
		case LHA_OS_TYPE_UNIX:
		case LHA_OS_TYPE_OS9_68K:
			process_level0_unix_area(header, data, data_len);
			break;

		case LHA_OS_TYPE_OS9:
			process_level0_os9_area(header, data, data_len);
			break;

		default:
			break;
	}
}

Project: lhasa

static int process_level0_path(LHAFileHeader *header, uint8_t *data,
                               size_t data_len)
{
	unsigned int i;


	if (data_len == 0) {
		return 1;
	}

	header->filename = malloc(data_len + 1);

	if (header->filename == NULL) {
		return 0;
	}

	memcpy(header->filename, data, data_len);
	header->filename[data_len] = '\0';


	for (i = 0; i < data_len; ++i) {
		if (header->filename[i] == '\\') {
			header->filename[i] = '/';
		}
	}

	return split_header_filename(header);
}

Project: lhasa

static void process_level0_unix_area(LHAFileHeader *header,
                                     uint8_t *data, size_t data_len)
{


	if (data_len < LEVEL_0_UNIX_EXTENDED_LEN || data[1] != 0x00) {
		return;
	}



	header->os_type = data[0];
	header->timestamp = lha_decode_uint32(data + 2);

	header->unix_perms = lha_decode_uint16(data + data_len - 6);
	header->unix_uid = lha_decode_uint16(data + data_len - 4);
	header->unix_gid = lha_decode_uint16(data + data_len - 2);

	header->extra_flags |= LHA_FILE_UNIX_PERMS | LHA_FILE_UNIX_UID_GID;
}

Project: lhasa

static int read_l1_extended_headers(LHAFileHeader **header,
                                    LHAInputStream *stream)
{
	uint8_t *ext_header;
	size_t ext_header_len;

	for (;;) {

		if (!read_next_ext_header(header, stream,
		                          &ext_header, &ext_header_len)) {
			return 0;
		}


		if (ext_header_len == 0) {
			break;
		}


		if ((*header)->compressed_length < ext_header_len) {
			return 0;
		}

		(*header)->compressed_length -= ext_header_len;


		if (ext_header_len < 3) {
			return 0;
		}
	}

	return 1;
}

Project: lhasa

static int split_header_filename(LHAFileHeader *header)
{
	char *sep;
	char *new_filename;


	sep = strrchr(header->filename, '/');

	if (sep != NULL) {
		new_filename = strdup(sep + 1);

		if (new_filename == NULL) {
			return 0;
		}

		*(sep + 1) = '\0';
		header->path = header->filename;
		header->filename = new_filename;
	}

	return 1;
}