CVE-2018-12418 (published 2018-06-14T16:29Z)

Archive.java in Junrar before 1.0.1, as used in Apache Tika and other products, is affected by a denial of service vulnerability due to an infinite loop when handling corrupt RAR files.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 5.5, MEDIUM (CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-835
cwe_name: Loop with Unreachable Exit Condition ('Infinite Loop')
description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
extended_description: If the loop can be influenced by an attacker, this weakness could allow attackers to consume excessive resources such as CPU or memory.
         

Fix information:
repo_url: https://github.com/junrar/junrar
commit_details: repo_url: https://github.com/junrar/junrar
author: Tim Allison
author_date: 2018-05-30 22:22:23-04:00
msg: make Archive slightly more robust against corrupt files.  Add (#8)

unit test file from Apache Tika.
repository: repo_url: https://github.com/junrar/junrar
repo_name: junrar/junrar
description: Plain Java unrar library
repo_language: Java
owner: junrar
file_changes: filename: Archive.java
change_type: ModificationType.MODIFY
diff: @@ -26,7 +26,9 @@ import java.io.OutputStream;
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -62,6 +64,8 @@ import com.github.junrar.unpack.Unpack;
 public class Archive implements Closeable {
 	private static Logger logger = Logger.getLogger(Archive.class.getName());
 
+	private static int MAX_HEADER_SIZE = 20971520;//20MB
+
 	private IReadOnlyAccess rof;
 
 	private final UnrarCallback unrarCallback;
@@ -229,11 +233,13 @@ public class Archive implements Closeable {
 		headers.clear();
 		currentHeaderIndex = 0;
 		int toRead = 0;
-
+		//keep track of positions already processed for
+		//more robustness against corrupt files
+		Set<Long> processedPositions = new HashSet<Long>();
 		while (true) {
 			int size = 0;
 			long newpos = 0;
-			byte[] baseBlockBuffer = new byte[BaseBlock.BaseBlockSize];
+			byte[] baseBlockBuffer = safelyAllocate(BaseBlock.BaseBlockSize, MAX_HEADER_SIZE);
 
 			long position = rof.getPosition();
 
@@ -266,7 +272,7 @@ public class Archive implements Closeable {
 			case MainHeader:
 				toRead = block.hasEncryptVersion() ? MainHeader.mainHeaderSizeWithEnc
 						: MainHeader.mainHeaderSize;
-				byte[] mainbuff = new byte[toRead];
+				byte[] mainbuff = safelyAllocate(toRead, MAX_HEADER_SIZE);
 				rof.readFully(mainbuff, toRead);
 				MainHeader mainhead = new MainHeader(block, mainbuff);
 				headers.add(mainhead);
@@ -280,7 +286,7 @@ public class Archive implements Closeable {
 
 			case SignHeader:
 				toRead = SignHeader.signHeaderSize;
-				byte[] signBuff = new byte[toRead];
+				byte[] signBuff = safelyAllocate(toRead, MAX_HEADER_SIZE);
 				rof.readFully(signBuff, toRead);
 				SignHeader signHead = new SignHeader(block, signBuff);
 				headers.add(signHead);
@@ -290,7 +296,7 @@ public class Archive implements Closeable {
 
 			case AvHeader:
 				toRead = AVHeader.avHeaderSize;
-				byte[] avBuff = new byte[toRead];
+				byte[] avBuff = safelyAllocate(toRead, MAX_HEADER_SIZE);
 				rof.readFully(avBuff, toRead);
 				AVHeader avHead = new AVHeader(block, avBuff);
 				headers.add(avHead);
@@ -299,7 +305,7 @@ public class Archive implements Closeable {
 
 			case CommHeader:
 				toRead = CommentHeader.commentHeaderSize;
-				byte[] commBuff = new byte[toRead];
+				byte[] commBuff = safelyAllocate(toRead, MAX_HEADER_SIZE);
 				rof.readFully(commBuff, toRead);
 				CommentHeader commHead = new CommentHeader(block, commBuff);
 				headers.add(commHead);
@@ -307,6 +313,10 @@ public class Archive implements Closeable {
 				// Integer.toHexString(commHead.getUnpMethod()));
 				newpos = commHead.getPositionInFile()
 						+ commHead.getHeaderSize();
+				if (processedPositions.contains(newpos)) {
+					throw new RarException(RarExceptionType.badRarArchive);
+				}
+				processedPositions.add(newpos);
 				rof.setPosition(newpos);
 
 				break;
@@ -321,7 +331,7 @@ public class Archive implements Closeable {
 				}
 				EndArcHeader endArcHead;
 				if (toRead > 0) {
-					byte[] endArchBuff = new byte[toRead];
+					byte[] endArchBuff = safelyAllocate(toRead, MAX_HEADER_SIZE);
 					rof.readFully(endArchBuff, toRead);
 					endArcHead = new EndArcHeader(block, endArchBuff);
 					// logger.info("HeaderType: endarch\ndatacrc:"+
@@ -335,7 +345,7 @@ public class Archive implements Closeable {
 				return;
 
 			default:
-				byte[] blockHeaderBuffer = new byte[BlockHeader.blockHeaderSize];
+				byte[] blockHeaderBuffer = safelyAllocate(BlockHeader.blockHeaderSize, MAX_HEADER_SIZE);
 				rof.readFully(blockHeaderBuffer, BlockHeader.blockHeaderSize);
 				BlockHeader blockHead = new BlockHeader(block,
 						blockHeaderBuffer);
@@ -346,13 +356,17 @@ public class Archive implements Closeable {
 					toRead = blockHead.getHeaderSize()
 							- BlockHeader.BaseBlockSize
 							- BlockHeader.blockHeaderSize;
-					byte[] fileHeaderBuffer = new byte[toRead];
+					byte[] fileHeaderBuffer = safelyAllocate(toRead, MAX_HEADER_SIZE);
 					rof.readFully(fileHeaderBuffer, toRead);
 
 					FileHeader fh = new FileHeader(blockHead, fileHeaderBuffer);
 					headers.add(fh);
 					newpos = fh.getPositionInFile() + fh.getHeaderSize()
 							+ fh.getFullPackSize();
+					if (processedPositions.contains(newpos)) {
+						throw new RarException(RarExceptionType.badRarArchive);
+					}
+					processedPositions.add(newpos);
 					rof.setPosition(newpos);
 					break;
 
@@ -360,18 +374,21 @@ public class Archive implements Closeable {
 					toRead = blockHead.getHeaderSize()
 							- BlockHeader.BaseBlockSize
 							- BlockHeader.blockHeaderSize;
-					byte[] protectHeaderBuffer = new byte[toRead];
+					byte[] protectHeaderBuffer = safelyAllocate(toRead, MAX_HEADER_SIZE);
 					rof.readFully(protectHeaderBuffer, toRead);
 					ProtectHeader ph = new ProtectHeader(blockHead,
 							protectHeaderBuffer);
-
 					newpos = ph.getPositionInFile() + ph.getHeaderSize()
 							+ ph.getDataSize();
+					if (processedPositions.contains(newpos)) {
+						throw new RarException(RarExceptionType.badRarArchive);
+					}
+					processedPositions.add(newpos);
 					rof.setPosition(newpos);
 					break;
 
 				case SubHeader: {
-					byte[] subHeadbuffer = new byte[SubBlockHeader.SubBlockHeaderSize];
+					byte[] subHeadbuffer = safelyAllocate(SubBlockHeader.SubBlockHeaderSize, MAX_HEADER_SIZE);
 					rof.readFully(subHeadbuffer,
 							SubBlockHeader.SubBlockHeaderSize);
 					SubBlockHeader subHead = new SubBlockHeader(blockHead,
@@ -379,7 +396,7 @@ public class Archive implements Closeable {
 					subHead.print();
 					switch (subHead.getSubType()) {
 					case MAC_HEAD: {
-						byte[] macHeaderbuffer = new byte[MacInfoHeader.MacInfoHeaderSize];
+						byte[] macHeaderbuffer = safelyAllocate(MacInfoHeader.MacInfoHeaderSize, MAX_HEADER_SIZE);
 						rof.readFully(macHeaderbuffer,
 								MacInfoHeader.MacInfoHeaderSize);
 						MacInfoHeader macHeader = new MacInfoHeader(subHead,
@@ -393,7 +410,7 @@ public class Archive implements Closeable {
 					case BEEA_HEAD:
 						break;
 					case EA_HEAD: {
-						byte[] eaHeaderBuffer = new byte[EAHeader.EAHeaderSize];
+						byte[] eaHeaderBuffer = safelyAllocate(EAHeader.EAHeaderSize, MAX_HEADER_SIZE);
 						rof.readFully(eaHeaderBuffer, EAHeader.EAHeaderSize);
 						EAHeader eaHeader = new EAHeader(subHead,
 								eaHeaderBuffer);
@@ -411,7 +428,7 @@ public class Archive implements Closeable {
 						toRead -= BaseBlock.BaseBlockSize;
 						toRead -= BlockHeader.blockHeaderSize;
 						toRead -= SubBlockHeader.SubBlockHeaderSize;
-						byte[] uoHeaderBuffer = new byte[toRead];
+						byte[] uoHeaderBuffer = safelyAllocate(toRead, MAX_HEADER_SIZE);
 						rof.readFully(uoHeaderBuffer, toRead);
 						UnixOwnersHeader uoHeader = new UnixOwnersHeader(
 								subHead, uoHeaderBuffer);
@@ -434,6 +451,16 @@ public class Archive implements Closeable {
 		}
 	}
 
+	private static byte[] safelyAllocate(long len, int maxSize) throws RarException {
+		if (maxSize < 0) {
+			throw new IllegalArgumentException("maxsize must be >= 0");
+		}
+		if (len < 0 || len > (long)maxSize) {
+			throw new RarException(RarExceptionType.badRarArchive);
+		}
+		return new byte[(int)len];
+	}
+
 	/**
 	 * Extract the file specified by the given header and write it to the
 	 * supplied output stream

programming_language: Java
filename: SimpleTest.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.junrar.testUtil;
+
+import com.github.junrar.Archive;
+import com.github.junrar.rarfile.FileHeader;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class SimpleTest {
+
+    @Test
+    public void testTikaDocs() throws Exception {
+        String[] expected = {"testEXCEL.xls", "13824",
+                "testHTML.html", "167",
+                "testOpenOffice2.odt", "26448",
+                "testPDF.pdf", "34824",
+                "testPPT.ppt", "16384",
+                "testRTF.rtf", "3410",
+                "testTXT.txt", "49",
+                "testWORD.doc", "19456",
+                "testXML.xml", "766"};
+
+
+        File f = new File(getClass().getResource("test-documents.rar").toURI());
+        Archive archive = null;
+        try {
+            archive = new Archive(f);
+
+            FileHeader fileHeader = archive.nextFileHeader();
+            int i = 0;
+            while (fileHeader != null) {
+                assertTrue(fileHeader.getFileNameString().contains(expected[i++]));
+                assertEquals(Long.parseLong(expected[i++]), fileHeader.getUnpSize());
+                fileHeader = archive.nextFileHeader();
+            }
+        } finally {
+            archive.close();
+        }
+
+    }
+}

programming_language: Java