8251329: (zipfs) Files.walkFileTree walks infinitely if zip has dir named "." inside

Reviewed-by: alanb, naoto
This commit is contained in:
Lance Andersen 2021-08-02 15:47:16 +00:00
parent 7a4c754e5d
commit 3e3051e2ee
3 changed files with 201 additions and 1 deletions

View File

@ -1575,6 +1575,10 @@ class ZipFileSystem extends FileSystem {
throw new ZipException("invalid CEN header (bad header size)");
}
IndexNode inode = new IndexNode(cen, pos, nlen);
if (hasDotOrDotDot(inode.name)) {
throw new ZipException("ZIP file can't be opened as a file system " +
"because an entry has a '.' or '..' element in its name");
}
inodes.put(inode, inode);
if (zc.isUTF8() || (flag & FLAG_USE_UTF8) != 0) {
checkUTF8(inode.name);
@ -1591,6 +1595,44 @@ class ZipFileSystem extends FileSystem {
return cen;
}
/**
* Check Inode.name to see if it includes a "." or ".." in the name array
* @param path the path as stored in Inode.name to verify
* @return true if the path contains a "." or ".." entry; false otherwise
*/
private boolean hasDotOrDotDot(byte[] path) {
// Inode.name always includes "/" in path[0]
assert path[0] == '/';
if (path.length == 1) {
return false;
}
int index = 1;
while (index < path.length) {
int starting = index;
while (index < path.length && path[index] != '/') {
index++;
}
// Check the path snippet for a "." or ".."
if (isDotOrDotDotPath(path, starting, index)) {
return true;
}
index++;
}
return false;
}
/**
* Check the path to see if it includes a "." or ".."
* @param path the path to check
* @return true if the path contains a "." or ".." entry; false otherwise
*/
private boolean isDotOrDotDotPath(byte[] path, int start, int index) {
int pathLen = index - start;
if ((pathLen == 1 && path[start] == '.'))
return true;
return (pathLen == 2 && path[start] == '.') && path[start + 1] == '.';
}
private final void checkUTF8(byte[] a) throws ZipException {
try {
int end = a.length;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -45,6 +45,9 @@ import java.util.Set;
* <li>Open an existing file as a Zip file system</li>
* </ul>
*
* The Zip file system provider does not support opening an existing Zip file
* that contains entries with "." or ".." in its name elements.
*
* <h2>URI Scheme Used to Identify the Zip File System</h2>
*
* The URI {@link java.net.URI#getScheme scheme} that identifies the ZIP file system is {@code jar}.

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Stream;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
/**
* @test
* @bug 8251329
* @summary Excercise Zip FS with "." or ".." in a Zip Entry name
* @modules jdk.zipfs
* @run testng/othervm HasDotDotTest
*/
public class HasDotDotTest {
// Zip file to be created
private static final Path ZIPFILE = Path.of("zipfsDotDotTest.zip");
// Data for Zip entries
private static final byte[] ENTRY_DATA =
"Tennis Anyone".getBytes(StandardCharsets.UTF_8);
// Display output
private static final boolean DEBUG = false;
/**
* DataProvider containing Zip entry names which should result in an IOException
* @return Array of Zip entry names
*/
@DataProvider
private Object[][] checkForDotOrDotDotPaths() {
return new Object[][]{
{"/./foo"},
{"/../foo"},
{"/../foo/.."},
{"/foo/.."},
{"/foo/."},
{"/.."},
{"/."},
{"/.foo/./"},
{"/.././"},
};
}
// Zip entry names to create a Zip file with for validating they are not
// interpreted as a "." or ".." entry
private final String[] VALID_PATHS =
{"/foo.txt", "/..foo.txt", "/.foo.txt", "/.foo/bar.txt", "/foo/bar.txt"};
// Paths to be returned from Files::walk via Zip FS
private final String[] EXPECTED_PATHS =
{"/", "/..foo.txt", "/foo.txt", "/.foo.txt", "/.foo",
"/.foo/bar.txt", "/foo/bar.txt", "/foo"};
/**
* Creates a Zip file
* @param zip path for Zip to be created
* @param entries the entries to add to the Zip file
* @throws IOException if an error occurs
*/
private static void createZip(Path zip, String... entries) throws IOException {
try (var os = Files.newOutputStream(zip);
ZipOutputStream zos = new ZipOutputStream(os)) {
for (var e : entries) {
var ze = new ZipEntry(e);
var crc = new CRC32();
ze.setMethod(ZipEntry.STORED);
crc.update(ENTRY_DATA);
ze.setCrc(crc.getValue());
ze.setSize(ENTRY_DATA.length);
zos.putNextEntry(ze);
zos.write(ENTRY_DATA);
}
}
}
/**
* Test to validate an IOException is thrown when opening a Zip file using
* Zip FS and the path contains a "." or ".."
* @param path
* @throws IOException
*/
@Test(dataProvider = "checkForDotOrDotDotPaths")
public void hasDotOrDotDotTest(String path) throws IOException {
if (DEBUG) {
System.out.printf("Validating entry: %s%n", path);
}
Files.deleteIfExists(ZIPFILE);
createZip(ZIPFILE, path);
assertThrows(IOException.class, () ->
FileSystems.newFileSystem(ZIPFILE, Map.of()));
Files.deleteIfExists(ZIPFILE);
}
/**
* Validate that an entry with a name containing a "." or ".." can be
* accessed via Files::walk
* @throws IOException if an error occurs
*/
@Test
public void validPaths() throws IOException {
Files.deleteIfExists(ZIPFILE);
createZip(ZIPFILE, VALID_PATHS);
/*
Walk through the Zip file and collect the Zip FS entries
*/
try (FileSystem zipfs = FileSystems.newFileSystem(ZIPFILE)) {
Path zipRoot = zipfs.getPath("/");
try (Stream<Path> files = Files.walk(zipRoot, Integer.MAX_VALUE)) {
var entries = files.map(Path::toString)
.sorted()
.toArray(String[]::new);
if (DEBUG) {
for (String zipEntry : entries) {
System.out.println(zipEntry);
}
}
Arrays.sort(EXPECTED_PATHS);
assertTrue(Arrays.equals(entries, EXPECTED_PATHS));
}
}
Files.deleteIfExists(ZIPFILE);
}
}