From 05ebb55896d10a9737dad9ae0303f7f45489ba6f Mon Sep 17 00:00:00 2001
From: Grzegorz Antoniak <ga@anadoxin.org>
Date: Sat, 13 Feb 2021 09:08:13 +0100
Subject: [PATCH] RAR5 reader: fixed out of bounds read in some files

Added more range checks in the bit stream reading functions
(read_bits_16 and read_bits_32) in order to better guard against out of
memory reads.

This commit contains a test with OSSFuzz sample #30448.

Upstream-Status: Backport [https://git.launchpad.net/ubuntu/+source/libarchive/plain/debian/patches/CVE-2021-36976-1.patch?h=applied/3.4.3-2ubuntu0.1]
CVE: CVE-2021-36976
Signed-off-by: Virendra Thakur <virendra.thakur@kpit.com>
---
 Makefile.am                                   |   1 +
 libarchive/archive_read_support_format_rar5.c | 108 ++++++++++--------
 libarchive/test/test_read_format_rar5.c       |  16 +++
 ...r5_decode_number_out_of_bounds_read.rar.uu |  10 ++
 4 files changed, 89 insertions(+), 46 deletions(-)
 create mode 100644 libarchive/test/test_read_format_rar5_decode_number_out_of_bounds_read.rar.uu

--- a/Makefile.am
+++ b/Makefile.am
@@ -883,6 +883,7 @@ libarchive_test_EXTRA_DIST=\
 	libarchive/test/test_read_format_rar5_arm_filter_on_window_boundary.rar.uu \
 	libarchive/test/test_read_format_rar5_different_winsize_on_merge.rar.uu \
 	libarchive/test/test_read_format_rar5_block_size_is_too_small.rar.uu \
+	libarchive/test/test_read_format_rar5_decode_number_out_of_bounds_read.rar.uu \
 	libarchive/test/test_read_format_raw.bufr.uu \
 	libarchive/test/test_read_format_raw.data.gz.uu \
 	libarchive/test/test_read_format_raw.data.Z.uu \
--- a/libarchive/archive_read_support_format_rar5.c
+++ b/libarchive/archive_read_support_format_rar5.c
@@ -1012,7 +1012,16 @@ static int read_var_sized(struct archive
 	return ret;
 }
 
-static int read_bits_32(struct rar5* rar, const uint8_t* p, uint32_t* value) {
+static int read_bits_32(struct archive_read* a, struct rar5* rar,
+	const uint8_t* p, uint32_t* value)
+{
+	if(rar->bits.in_addr >= rar->cstate.cur_block_size) {
+		archive_set_error(&a->archive,
+			ARCHIVE_ERRNO_PROGRAMMER,
+			"Premature end of stream during extraction of data (#1)");
+		return ARCHIVE_FATAL;
+	}
+
 	uint32_t bits = ((uint32_t) p[rar->bits.in_addr]) << 24;
 	bits |= p[rar->bits.in_addr + 1] << 16;
 	bits |= p[rar->bits.in_addr + 2] << 8;
@@ -1023,7 +1032,16 @@ static int read_bits_32(struct rar5* rar
 	return ARCHIVE_OK;
 }
 
-static int read_bits_16(struct rar5* rar, const uint8_t* p, uint16_t* value) {
+static int read_bits_16(struct archive_read* a, struct rar5* rar,
+	const uint8_t* p, uint16_t* value)
+{
+	if(rar->bits.in_addr >= rar->cstate.cur_block_size) {
+		archive_set_error(&a->archive,
+			ARCHIVE_ERRNO_PROGRAMMER,
+			"Premature end of stream during extraction of data (#2)");
+		return ARCHIVE_FATAL;
+	}
+
 	int bits = (int) ((uint32_t) p[rar->bits.in_addr]) << 16;
 	bits |= (int) p[rar->bits.in_addr + 1] << 8;
 	bits |= (int) p[rar->bits.in_addr + 2];
@@ -1039,8 +1057,8 @@ static void skip_bits(struct rar5* rar,
 }
 
 /* n = up to 16 */
-static int read_consume_bits(struct rar5* rar, const uint8_t* p, int n,
-    int* value)
+static int read_consume_bits(struct archive_read* a, struct rar5* rar,
+	const uint8_t* p, int n, int* value)
 {
 	uint16_t v;
 	int ret, num;
@@ -1051,7 +1069,7 @@ static int read_consume_bits(struct rar5
 		return ARCHIVE_FATAL;
 	}
 
-	ret = read_bits_16(rar, p, &v);
+	ret = read_bits_16(a, rar, p, &v);
 	if(ret != ARCHIVE_OK)
 		return ret;
 
@@ -2425,13 +2443,13 @@ static int create_decode_tables(uint8_t*
 static int decode_number(struct archive_read* a, struct decode_table* table,
     const uint8_t* p, uint16_t* num)
 {
-	int i, bits, dist;
+	int i, bits, dist, ret;
 	uint16_t bitfield;
 	uint32_t pos;
 	struct rar5* rar = get_context(a);
 
-	if(ARCHIVE_OK != read_bits_16(rar, p, &bitfield)) {
-		return ARCHIVE_EOF;
+	if(ARCHIVE_OK != (ret = read_bits_16(a, rar, p, &bitfield))) {
+		return ret;
 	}
 
 	bitfield &= 0xfffe;
@@ -2537,14 +2555,6 @@ static int parse_tables(struct archive_r
 	for(i = 0; i < HUFF_TABLE_SIZE;) {
 		uint16_t num;
 
-		if((rar->bits.in_addr + 6) >= rar->cstate.cur_block_size) {
-			/* Truncated data, can't continue. */
-			archive_set_error(&a->archive,
-			    ARCHIVE_ERRNO_FILE_FORMAT,
-			    "Truncated data in huffman tables (#2)");
-			return ARCHIVE_FATAL;
-		}
-
 		ret = decode_number(a, &rar->cstate.bd, p, &num);
 		if(ret != ARCHIVE_OK) {
 			archive_set_error(&a->archive,
@@ -2561,8 +2571,8 @@ static int parse_tables(struct archive_r
 			/* 16..17: repeat previous code */
 			uint16_t n;
 
-			if(ARCHIVE_OK != read_bits_16(rar, p, &n))
-				return ARCHIVE_EOF;
+			if(ARCHIVE_OK != (ret = read_bits_16(a, rar, p, &n)))
+				return ret;
 
 			if(num == 16) {
 				n >>= 13;
@@ -2590,8 +2600,8 @@ static int parse_tables(struct archive_r
 			/* other codes: fill with zeroes `n` times */
 			uint16_t n;
 
-			if(ARCHIVE_OK != read_bits_16(rar, p, &n))
-				return ARCHIVE_EOF;
+			if(ARCHIVE_OK != (ret = read_bits_16(a, rar, p, &n)))
+				return ret;
 
 			if(num == 18) {
 				n >>= 13;
@@ -2707,22 +2717,22 @@ static int parse_block_header(struct arc
 }
 
 /* Convenience function used during filter processing. */
-static int parse_filter_data(struct rar5* rar, const uint8_t* p,
-    uint32_t* filter_data)
+static int parse_filter_data(struct archive_read* a, struct rar5* rar,
+	const uint8_t* p, uint32_t* filter_data)
 {
-	int i, bytes;
+	int i, bytes, ret;
 	uint32_t data = 0;
 
-	if(ARCHIVE_OK != read_consume_bits(rar, p, 2, &bytes))
-		return ARCHIVE_EOF;
+	if(ARCHIVE_OK != (ret = read_consume_bits(a, rar, p, 2, &bytes)))
+		return ret;
 
 	bytes++;
 
 	for(i = 0; i < bytes; i++) {
 		uint16_t byte;
 
-		if(ARCHIVE_OK != read_bits_16(rar, p, &byte)) {
-			return ARCHIVE_EOF;
+		if(ARCHIVE_OK != (ret = read_bits_16(a, rar, p, &byte))) {
+			return ret;
 		}
 
 		/* Cast to uint32_t will ensure the shift operation will not
@@ -2765,16 +2775,17 @@ static int parse_filter(struct archive_r
 	uint16_t filter_type;
 	struct filter_info* filt = NULL;
 	struct rar5* rar = get_context(ar);
+	int ret;
 
 	/* Read the parameters from the input stream. */
-	if(ARCHIVE_OK != parse_filter_data(rar, p, &block_start))
-		return ARCHIVE_EOF;
+	if(ARCHIVE_OK != (ret = parse_filter_data(ar, rar, p, &block_start)))
+		return ret;
 
-	if(ARCHIVE_OK != parse_filter_data(rar, p, &block_length))
-		return ARCHIVE_EOF;
+	if(ARCHIVE_OK != (ret = parse_filter_data(ar, rar, p, &block_length)))
+		return ret;
 
-	if(ARCHIVE_OK != read_bits_16(rar, p, &filter_type))
-		return ARCHIVE_EOF;
+	if(ARCHIVE_OK != (ret = read_bits_16(ar, rar, p, &filter_type)))
+		return ret;
 
 	filter_type >>= 13;
 	skip_bits(rar, 3);
@@ -2814,8 +2825,8 @@ static int parse_filter(struct archive_r
 	if(filter_type == FILTER_DELTA) {
 		int channels;
 
-		if(ARCHIVE_OK != read_consume_bits(rar, p, 5, &channels))
-			return ARCHIVE_EOF;
+		if(ARCHIVE_OK != (ret = read_consume_bits(ar, rar, p, 5, &channels)))
+			return ret;
 
 		filt->channels = channels + 1;
 	}
@@ -2823,10 +2834,11 @@ static int parse_filter(struct archive_r
 	return ARCHIVE_OK;
 }
 
-static int decode_code_length(struct rar5* rar, const uint8_t* p,
-    uint16_t code)
+static int decode_code_length(struct archive_read* a, struct rar5* rar,
+	const uint8_t* p, uint16_t code)
 {
 	int lbits, length = 2;
+
 	if(code < 8) {
 		lbits = 0;
 		length += code;
@@ -2838,7 +2850,7 @@ static int decode_code_length(struct rar
 	if(lbits > 0) {
 		int add;
 
-		if(ARCHIVE_OK != read_consume_bits(rar, p, lbits, &add))
+		if(ARCHIVE_OK != read_consume_bits(a, rar, p, lbits, &add))
 			return -1;
 
 		length += add;
@@ -2933,7 +2945,7 @@ static int do_uncompress_block(struct ar
 			continue;
 		} else if(num >= 262) {
 			uint16_t dist_slot;
-			int len = decode_code_length(rar, p, num - 262),
+			int len = decode_code_length(a, rar, p, num - 262),
 				dbits,
 				dist = 1;
 
@@ -2975,12 +2987,12 @@ static int do_uncompress_block(struct ar
 					uint16_t low_dist;
 
 					if(dbits > 4) {
-						if(ARCHIVE_OK != read_bits_32(
-						    rar, p, &add)) {
+						if(ARCHIVE_OK != (ret = read_bits_32(
+						    a, rar, p, &add))) {
 							/* Return EOF if we
 							 * can't read more
 							 * data. */
-							return ARCHIVE_EOF;
+							return ret;
 						}
 
 						skip_bits(rar, dbits - 4);
@@ -3015,11 +3027,11 @@ static int do_uncompress_block(struct ar
 					/* dbits is one of [0,1,2,3] */
 					int add;
 
-					if(ARCHIVE_OK != read_consume_bits(rar,
-					     p, dbits, &add)) {
+					if(ARCHIVE_OK != (ret = read_consume_bits(a, rar,
+					     p, dbits, &add))) {
 						/* Return EOF if we can't read
 						 * more data. */
-						return ARCHIVE_EOF;
+						return ret;
 					}
 
 					dist += add;
@@ -3076,7 +3088,11 @@ static int do_uncompress_block(struct ar
 				return ARCHIVE_FATAL;
 			}
 
-			len = decode_code_length(rar, p, len_slot);
+			len = decode_code_length(a, rar, p, len_slot);
+			if (len == -1) {
+				return ARCHIVE_FATAL;
+			}
+
 			rar->cstate.last_len = len;
 
 			if(ARCHIVE_OK != copy_string(a, len, dist))
--- a/libarchive/test/test_read_format_rar5.c
+++ b/libarchive/test/test_read_format_rar5.c
@@ -1271,3 +1271,20 @@ DEFINE_TEST(test_read_format_rar5_block_
 
 	EPILOGUE();
 }
+
+DEFINE_TEST(test_read_format_rar5_decode_number_out_of_bounds_read)
+{
+	/* oss fuzz 30448 */
+
+	char buf[4096];
+	PROLOGUE("test_read_format_rar5_decode_number_out_of_bounds_read.rar");
+
+	/* Return codes of those calls are ignored, because this sample file
+	 * is invalid. However, the unpacker shouldn't produce any SIGSEGV
+	 * errors during processing. */
+
+	(void) archive_read_next_header(a, &ae);
+	while(0 < archive_read_data(a, buf, sizeof(buf))) {}
+
+	EPILOGUE();
+}
--- /dev/null
+++ b/libarchive/test/test_read_format_rar5_decode_number_out_of_bounds_read.rar.uu
@@ -0,0 +1,10 @@
+begin 644 test_read_format_rar5_decode_number_out_of_bounds_read.rar
+M4F%R(1H'`0!3@"KT`P+G(@(0("`@@`L!!"`@("`@(($D_[BJ2"!::7!)210V
+M+0#ZF#)Q!`+>YPW_("`@("``_R````````````````````````````!__P``
+M``````!T72`@/EW_(/\@("`@("`@("`@("`@("`@("`@("`@("`@(/\@("`@
+M("`@("#_("`@("`@("`@("`@("`@("`@("`@("`@("#_("`@("`@("`@_R`@
+M("`@("`@("`@("`@("`@("`@("`@("`@_R`@("`@("`@(/\@("`@("`@("`@
+M("`@("`@("`@("`@("`@(/\@("`@("`@("#_("`@("`@("`@("`@("`@("`@
+E("`@("`@("#_("`@("`@("`@_R`@("`@("`@("`@("`@("`@(```
+`
+end