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); + } + }