This is an automated email from the ASF dual-hosted git repository.

bodewig pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git

commit ecf514b46342c783586c2703a5b0f371092933c3
Author: Stefan Bodewig <bode...@apache.org>
AuthorDate: Sat May 1 11:29:35 2021 +0200

    COMPRESS-575 harden parser of PAX 0.1 GNU sparse map
---
 .../archivers/tar/TarArchiveInputStream.java       |  2 +-
 .../commons/compress/archivers/tar/TarFile.java    |  2 +-
 .../commons/compress/archivers/tar/TarUtils.java   | 51 ++++++++++++++++--
 .../compress/archivers/tar/TarUtilsTest.java       | 61 ++++++++++++++++++++++
 4 files changed, 111 insertions(+), 5 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java
 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java
index e67f1d7..4aaeba9 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java
@@ -607,7 +607,7 @@ public class TarArchiveInputStream extends 
ArchiveInputStream {
 
         // for 0.1 PAX Headers
         if (headers.containsKey("GNU.sparse.map")) {
-            sparseHeaders = 
TarUtils.parsePAX01SparseHeaders(headers.get("GNU.sparse.map"));
+            sparseHeaders = new 
ArrayList<>(TarUtils.parseFromPAX01SparseHeaders(headers.get("GNU.sparse.map")));
         }
         getNextEntry(); // Get the actual file entry
         if (currEntry == null) {
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java
index 6d4a4a8..9b82025 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java
@@ -431,7 +431,7 @@ public class TarFile implements Closeable {
 
         // for 0.1 PAX Headers
         if (headers.containsKey("GNU.sparse.map")) {
-            sparseHeaders = 
TarUtils.parsePAX01SparseHeaders(headers.get("GNU.sparse.map"));
+            sparseHeaders = new 
ArrayList<>(TarUtils.parseFromPAX01SparseHeaders(headers.get("GNU.sparse.map")));
         }
         getNextTarEntry(); // Get the actual file entry
         if (currEntry == null) {
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
index fe6fe4d..eee894e 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
@@ -803,20 +803,65 @@ public class TarUtils {
      * GNU.sparse.map
      *    Map of non-null data chunks. It is a string consisting of 
comma-separated values "offset,size[,offset-1,size-1...]"
      *
+     * <p>Will internally invoke {@link #parseFromPAX01SparseHeaders} and map 
IOExceptions to a RzuntimeException, You
+     * should use {@link #parseFromPAX01SparseHeaders} directly instead.
+     *
      * @param sparseMap the sparse map string consisting of comma-separated 
values "offset,size[,offset-1,size-1...]"
      * @return sparse headers parsed from sparse map
+     * @deprecated use #parseFromPAX01SparseHeaders instead
      */
     protected static List<TarArchiveStructSparse> 
parsePAX01SparseHeaders(String sparseMap) {
+        try {
+            return parseFromPAX01SparseHeaders(sparseMap);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+    }
+
+    /**
+     * For PAX Format 0.1, the sparse headers are stored in a single variable 
: GNU.sparse.map
+     * GNU.sparse.map
+     *    Map of non-null data chunks. It is a string consisting of 
comma-separated values "offset,size[,offset-1,size-1...]"
+     *
+     * @param sparseMap the sparse map string consisting of comma-separated 
values "offset,size[,offset-1,size-1...]"
+     * @return unmodifiable list of sparse headers parsed from sparse map
+     * @since 1.21
+     */
+    protected static List<TarArchiveStructSparse> 
parseFromPAX01SparseHeaders(String sparseMap)
+        throws IOException {
         List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
         String[] sparseHeaderStrings = sparseMap.split(",");
+        if (sparseHeaderStrings.length % 2 == 1) {
+            throw new IOException("Corrupted TAR archive. Bad format in 
GNU.sparse.map PAX Header");
+        }
 
         for (int i = 0; i < sparseHeaderStrings.length; i += 2) {
-            long sparseOffset = Long.parseLong(sparseHeaderStrings[i]);
-            long sparseNumbytes = Long.parseLong(sparseHeaderStrings[i + 1]);
+            long sparseOffset;
+            try {
+                sparseOffset = Long.parseLong(sparseHeaderStrings[i]);
+            } catch (NumberFormatException ex) {
+                throw new IOException("Corrupted TAR archive."
+                    + " Sparse struct offset contains a non-numeric value");
+            }
+            if (sparseOffset < 0) {
+                throw new IOException("Corrupted TAR archive."
+                    + " Sparse struct offset contains negative value");
+            }
+            long sparseNumbytes;
+            try {
+                sparseNumbytes = Long.parseLong(sparseHeaderStrings[i + 1]);
+            } catch (NumberFormatException ex) {
+                throw new IOException("Corrupted TAR archive."
+                    + " Sparse struct numbytes contains a non-numeric value");
+            }
+            if (sparseNumbytes < 0) {
+                throw new IOException("Corrupted TAR archive."
+                    + " Sparse struct numbytes contains negative value");
+            }
             sparseHeaders.add(new TarArchiveStructSparse(sparseOffset, 
sparseNumbytes));
         }
 
-        return sparseHeaders;
+        return Collections.unmodifiableList(sparseHeaders);
     }
 
     /**
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java 
b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
index 4e04f06..1727ecd 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
@@ -548,4 +548,65 @@ public class TarUtilsTest {
             null, Collections.emptyMap());
     }
 
+    @Test
+    public void parseFromPAX01SparseHeaders() throws Exception {
+        final String map = "0,10,20,0,20,5";
+        final List<TarArchiveStructSparse> sparse = 
TarUtils.parseFromPAX01SparseHeaders(map);
+        assertEquals(3, sparse.size());
+        assertEquals(0, sparse.get(0).getOffset());
+        assertEquals(10, sparse.get(0).getNumbytes());
+        assertEquals(20, sparse.get(1).getOffset());
+        assertEquals(0, sparse.get(1).getNumbytes());
+        assertEquals(20, sparse.get(2).getOffset());
+        assertEquals(5, sparse.get(2).getNumbytes());
+    }
+
+    @Test
+    public void parseFromPAX01SparseHeadersRejectsOddNumberOfEntries() throws 
Exception {
+        thrown.expect(IOException.class);
+        thrown.expectMessage(startsWith("Corrupted TAR archive"));
+        final String map = "0,10,20,0,20";
+        TarUtils.parseFromPAX01SparseHeaders(map);
+    }
+
+    @Test
+    public void parseFromPAX01SparseHeadersRejectsNonNumericOffset() throws 
Exception {
+        thrown.expect(IOException.class);
+        thrown.expectMessage(startsWith("Corrupted TAR archive"));
+        final String map = "0,10,20,0,2a,5";
+        TarUtils.parseFromPAX01SparseHeaders(map);
+    }
+
+    @Test
+    public void parseFromPAX01SparseHeadersRejectsNonNumericNumbytes() throws 
Exception {
+        thrown.expect(IOException.class);
+        thrown.expectMessage(startsWith("Corrupted TAR archive"));
+        final String map = "0,10,20,0,20,b";
+        TarUtils.parseFromPAX01SparseHeaders(map);
+    }
+
+    @Test
+    public void parseFromPAX01SparseHeadersRejectsNegativeOffset() throws 
Exception {
+        thrown.expect(IOException.class);
+        thrown.expectMessage(startsWith("Corrupted TAR archive"));
+        final String map = "0,10,20,0,-2,5";
+        TarUtils.parseFromPAX01SparseHeaders(map);
+    }
+
+    @Test
+    public void parseFromPAX01SparseHeadersRejectsNegativeNumbytes() throws 
Exception {
+        thrown.expect(IOException.class);
+        thrown.expectMessage(startsWith("Corrupted TAR archive"));
+        final String map = "0,10,20,0,20,-5";
+        TarUtils.parseFromPAX01SparseHeaders(map);
+    }
+
+    @Test
+    public void parsePAX01SparseHeadersRejectsOddNumberOfEntries() throws 
Exception {
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage(startsWith("Corrupted TAR archive"));
+        final String map = "0,10,20,0,20";
+        TarUtils.parsePAX01SparseHeaders(map);
+    }
+
 }

Reply via email to