undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
399
test/jdk/tools/jlink/plugins/CompressorPluginTest.java
Normal file
399
test/jdk/tools/jlink/plugins/CompressorPluginTest.java
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test zip compressor
|
||||
* @author Jean-Francois Denise
|
||||
* @modules java.base/jdk.internal.jimage.decompressor
|
||||
* jdk.jlink/jdk.tools.jlink.internal
|
||||
* jdk.jlink/jdk.tools.jlink.internal.plugins
|
||||
* jdk.jlink/jdk.tools.jlink.plugin
|
||||
* @run main CompressorPluginTest
|
||||
*/
|
||||
import java.net.URI;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystemNotFoundException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.ProviderNotFoundException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.jimage.decompressor.CompressedResourceHeader;
|
||||
import jdk.internal.jimage.decompressor.ResourceDecompressor;
|
||||
import jdk.internal.jimage.decompressor.ResourceDecompressorFactory;
|
||||
import jdk.internal.jimage.decompressor.StringSharingDecompressorFactory;
|
||||
import jdk.internal.jimage.decompressor.ZipDecompressorFactory;
|
||||
import jdk.tools.jlink.internal.ResourcePoolManager;
|
||||
import jdk.tools.jlink.internal.StringTable;
|
||||
import jdk.tools.jlink.internal.plugins.DefaultCompressPlugin;
|
||||
import jdk.tools.jlink.internal.plugins.StringSharingPlugin;
|
||||
import jdk.tools.jlink.internal.plugins.ZipPlugin;
|
||||
import jdk.tools.jlink.plugin.Plugin;
|
||||
import jdk.tools.jlink.plugin.ResourcePool;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolEntry;
|
||||
|
||||
public class CompressorPluginTest {
|
||||
|
||||
private static int strID = 1;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new CompressorPluginTest().test();
|
||||
}
|
||||
|
||||
public void test() throws Exception {
|
||||
FileSystem fs;
|
||||
try {
|
||||
fs = FileSystems.getFileSystem(URI.create("jrt:/"));
|
||||
} catch (ProviderNotFoundException | FileSystemNotFoundException e) {
|
||||
System.err.println("Not an image build, test skipped.");
|
||||
return;
|
||||
}
|
||||
Path javabase = fs.getPath("/modules/java.base");
|
||||
|
||||
checkCompress(gatherResources(javabase), new ZipPlugin(), null,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new ZipDecompressorFactory()
|
||||
});
|
||||
|
||||
ResourcePool classes = gatherClasses(javabase);
|
||||
// compress = String sharing
|
||||
checkCompress(classes, new StringSharingPlugin(), null,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new StringSharingDecompressorFactory()});
|
||||
|
||||
// compress level 0 == no compression
|
||||
Properties options0 = new Properties();
|
||||
DefaultCompressPlugin compressPlugin = new DefaultCompressPlugin();
|
||||
options0.setProperty(compressPlugin.getName(),
|
||||
DefaultCompressPlugin.LEVEL_0);
|
||||
checkCompress(classes, compressPlugin,
|
||||
options0,
|
||||
new ResourceDecompressorFactory[]{
|
||||
});
|
||||
|
||||
// compress level 1 == String sharing
|
||||
Properties options1 = new Properties();
|
||||
compressPlugin = new DefaultCompressPlugin();
|
||||
options1.setProperty(compressPlugin.getName(), DefaultCompressPlugin.LEVEL_1);
|
||||
checkCompress(classes, compressPlugin,
|
||||
options1,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new StringSharingDecompressorFactory()
|
||||
});
|
||||
|
||||
// compress level 1 == String sharing + filter
|
||||
options1.setProperty(DefaultCompressPlugin.FILTER,
|
||||
"**Exception.class");
|
||||
compressPlugin = new DefaultCompressPlugin();
|
||||
options1.setProperty(compressPlugin.getName(), DefaultCompressPlugin.LEVEL_1);
|
||||
checkCompress(classes, compressPlugin,
|
||||
options1,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new StringSharingDecompressorFactory()
|
||||
}, Collections.singletonList(".*Exception.class"));
|
||||
|
||||
// compress level 2 == ZIP
|
||||
Properties options2 = new Properties();
|
||||
options2.setProperty(DefaultCompressPlugin.FILTER,
|
||||
"**Exception.class");
|
||||
compressPlugin = new DefaultCompressPlugin();
|
||||
options2.setProperty(compressPlugin.getName(), DefaultCompressPlugin.LEVEL_2);
|
||||
checkCompress(classes, compressPlugin,
|
||||
options2,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new ZipDecompressorFactory()
|
||||
}, Collections.singletonList(".*Exception.class"));
|
||||
|
||||
// compress level 2 == ZIP + filter
|
||||
options2.setProperty(DefaultCompressPlugin.FILTER,
|
||||
"**Exception.class");
|
||||
compressPlugin = new DefaultCompressPlugin();
|
||||
options2.setProperty(compressPlugin.getName(), DefaultCompressPlugin.LEVEL_2);
|
||||
checkCompress(classes, compressPlugin,
|
||||
options2,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new ZipDecompressorFactory(),
|
||||
}, Collections.singletonList(".*Exception.class"));
|
||||
|
||||
// compress level zip-0 == no compression
|
||||
Properties optionsZip0 = new Properties();
|
||||
DefaultCompressPlugin compressPluginZip0 = new DefaultCompressPlugin();
|
||||
optionsZip0.setProperty(compressPluginZip0.getName(), "zip-0");
|
||||
checkCompress(classes, compressPluginZip0,
|
||||
optionsZip0,
|
||||
new ResourceDecompressorFactory[]{
|
||||
});
|
||||
|
||||
// compress level zip-[1-9] == varied compression levels
|
||||
for(int i = 1; i < 10; i++) {
|
||||
Properties optionsZip = new Properties();
|
||||
compressPlugin = new DefaultCompressPlugin();
|
||||
optionsZip.setProperty(compressPlugin.getName(), "zip-" + i);
|
||||
checkCompress(classes, compressPlugin,
|
||||
optionsZip,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new ZipDecompressorFactory(),
|
||||
});
|
||||
}
|
||||
|
||||
// compress level zip-[1-9] == varied compression levels + filter
|
||||
for(int i = 1; i < 10; i++) {
|
||||
Properties optionsZip = new Properties();
|
||||
compressPlugin = new DefaultCompressPlugin();
|
||||
optionsZip.setProperty(DefaultCompressPlugin.FILTER, "**Exception.class");
|
||||
optionsZip.setProperty(compressPlugin.getName(), "zip-" + i);
|
||||
checkCompress(classes, compressPlugin,
|
||||
optionsZip,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new ZipDecompressorFactory(),
|
||||
}, Collections.singletonList(".*Exception.class"));
|
||||
}
|
||||
|
||||
testBadCompressProps(classes, "zip-10");
|
||||
testBadCompressProps(classes, "zip-badarg");
|
||||
testBadCompressProps(classes, "zip-10000000");
|
||||
|
||||
}
|
||||
|
||||
private void testBadCompressProps(ResourcePool classes, String compressArg) throws Exception {
|
||||
Properties badProps = new Properties();
|
||||
DefaultCompressPlugin compressPlugin = new DefaultCompressPlugin();
|
||||
badProps.setProperty(compressPlugin.getName(), compressArg);
|
||||
try {
|
||||
checkCompress(classes, compressPlugin,
|
||||
badProps,
|
||||
new ResourceDecompressorFactory[]{
|
||||
new ZipDecompressorFactory(),
|
||||
});
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (e.getMessage().contains("Invalid compression level")) {
|
||||
return;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Expected compression IAE with " + compressArg + " but didn't get one.");
|
||||
|
||||
}
|
||||
|
||||
private ResourcePool gatherResources(Path module) throws Exception {
|
||||
ResourcePoolManager poolMgr = new ResourcePoolManager(ByteOrder.nativeOrder(), new StringTable() {
|
||||
|
||||
@Override
|
||||
public int addString(String str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int id) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ResourcePoolBuilder poolBuilder = poolMgr.resourcePoolBuilder();
|
||||
try (Stream<Path> stream = Files.walk(module)) {
|
||||
for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext();) {
|
||||
Path p = iterator.next();
|
||||
if (Files.isRegularFile(p)) {
|
||||
byte[] content = Files.readAllBytes(p);
|
||||
poolBuilder.add(ResourcePoolEntry.create(p.toString(), content));
|
||||
}
|
||||
}
|
||||
}
|
||||
return poolBuilder.build();
|
||||
}
|
||||
|
||||
private ResourcePool gatherClasses(Path module) throws Exception {
|
||||
ResourcePoolManager poolMgr = new ResourcePoolManager(ByteOrder.nativeOrder(), new StringTable() {
|
||||
|
||||
@Override
|
||||
public int addString(String str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int id) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ResourcePoolBuilder poolBuilder = poolMgr.resourcePoolBuilder();
|
||||
try (Stream<Path> stream = Files.walk(module)) {
|
||||
for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext();) {
|
||||
Path p = iterator.next();
|
||||
if (Files.isRegularFile(p) && p.toString().endsWith(".class")) {
|
||||
byte[] content = Files.readAllBytes(p);
|
||||
poolBuilder.add(ResourcePoolEntry.create(p.toString(), content));
|
||||
}
|
||||
}
|
||||
}
|
||||
return poolBuilder.build();
|
||||
}
|
||||
|
||||
private void checkCompress(ResourcePool resources, Plugin prov,
|
||||
Properties config,
|
||||
ResourceDecompressorFactory[] factories) throws Exception {
|
||||
checkCompress(resources, prov, config, factories, Collections.emptyList());
|
||||
}
|
||||
|
||||
private void checkCompress(ResourcePool resources, Plugin prov,
|
||||
Properties config,
|
||||
ResourceDecompressorFactory[] factories,
|
||||
List<String> includes) throws Exception {
|
||||
if (factories.length == 0) {
|
||||
// no compression, nothing to check!
|
||||
return;
|
||||
}
|
||||
|
||||
long[] original = new long[1];
|
||||
long[] compressed = new long[1];
|
||||
resources.entries().forEach(resource -> {
|
||||
List<Pattern> includesPatterns = includes.stream()
|
||||
.map(Pattern::compile)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, String> props = new HashMap<>();
|
||||
if (config != null) {
|
||||
for (String p : config.stringPropertyNames()) {
|
||||
props.put(p, config.getProperty(p));
|
||||
}
|
||||
}
|
||||
prov.configure(props);
|
||||
final Map<Integer, String> strings = new HashMap<>();
|
||||
ResourcePoolManager inputResourcesMgr = new ResourcePoolManager(ByteOrder.nativeOrder(), new StringTable() {
|
||||
@Override
|
||||
public int addString(String str) {
|
||||
int id = strID;
|
||||
strID += 1;
|
||||
strings.put(id, str);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int id) {
|
||||
return strings.get(id);
|
||||
}
|
||||
});
|
||||
inputResourcesMgr.add(resource);
|
||||
ResourcePool compressedResources = applyCompressor(prov, inputResourcesMgr, resource, includesPatterns);
|
||||
original[0] += resource.contentLength();
|
||||
compressed[0] += compressedResources.findEntry(resource.path()).get().contentLength();
|
||||
applyDecompressors(factories, inputResourcesMgr.resourcePool(), compressedResources, strings, includesPatterns);
|
||||
});
|
||||
String compressors = Stream.of(factories)
|
||||
.map(Object::getClass)
|
||||
.map(Class::getSimpleName)
|
||||
.collect(Collectors.joining(", "));
|
||||
String size = "Compressed size: " + compressed[0] + ", original size: " + original[0];
|
||||
System.out.println("Used " + compressors + ". " + size);
|
||||
if (original[0] <= compressed[0]) {
|
||||
throw new AssertionError("java.base not compressed.");
|
||||
}
|
||||
}
|
||||
|
||||
private ResourcePool applyCompressor(Plugin plugin,
|
||||
ResourcePoolManager inputResources,
|
||||
ResourcePoolEntry res,
|
||||
List<Pattern> includesPatterns) {
|
||||
ResourcePoolManager resMgr = new ResourcePoolManager(ByteOrder.nativeOrder(),
|
||||
inputResources.getStringTable());
|
||||
ResourcePool compressedResourcePool = plugin.transform(inputResources.resourcePool(),
|
||||
resMgr.resourcePoolBuilder());
|
||||
String path = res.path();
|
||||
ResourcePoolEntry compressed = compressedResourcePool.findEntry(path).get();
|
||||
CompressedResourceHeader header
|
||||
= CompressedResourceHeader.readFromResource(ByteOrder.nativeOrder(), compressed.contentBytes());
|
||||
if (isIncluded(includesPatterns, path)) {
|
||||
if (header == null) {
|
||||
throw new AssertionError("Path should be compressed: " + path);
|
||||
}
|
||||
if (header.getDecompressorNameOffset() == 0) {
|
||||
throw new AssertionError("Invalid plugin offset "
|
||||
+ header.getDecompressorNameOffset());
|
||||
}
|
||||
if (header.getResourceSize() <= 0) {
|
||||
throw new AssertionError("Invalid compressed size "
|
||||
+ header.getResourceSize());
|
||||
}
|
||||
} else if (header != null) {
|
||||
throw new AssertionError("Path should not be compressed: " + path);
|
||||
}
|
||||
return compressedResourcePool;
|
||||
}
|
||||
|
||||
private void applyDecompressors(ResourceDecompressorFactory[] decompressors,
|
||||
ResourcePool inputResources,
|
||||
ResourcePool compressedResources,
|
||||
Map<Integer, String> strings,
|
||||
List<Pattern> includesPatterns) {
|
||||
compressedResources.entries().forEach(compressed -> {
|
||||
CompressedResourceHeader header = CompressedResourceHeader.readFromResource(
|
||||
ByteOrder.nativeOrder(), compressed.contentBytes());
|
||||
String path = compressed.path();
|
||||
ResourcePoolEntry orig = inputResources.findEntry(path).get();
|
||||
if (!isIncluded(includesPatterns, path)) {
|
||||
return;
|
||||
}
|
||||
byte[] decompressed = compressed.contentBytes();
|
||||
for (ResourceDecompressorFactory factory : decompressors) {
|
||||
try {
|
||||
ResourceDecompressor decompressor = factory.newDecompressor();
|
||||
decompressed = decompressor.decompress(
|
||||
strings::get, decompressed,
|
||||
CompressedResourceHeader.getSize(), header.getUncompressedSize());
|
||||
} catch (Exception exp) {
|
||||
throw new RuntimeException(exp);
|
||||
}
|
||||
}
|
||||
|
||||
if (decompressed.length != orig.contentLength()) {
|
||||
throw new AssertionError("Invalid uncompressed size "
|
||||
+ header.getUncompressedSize());
|
||||
}
|
||||
byte[] origContent = orig.contentBytes();
|
||||
for (int i = 0; i < decompressed.length; i++) {
|
||||
if (decompressed[i] != origContent[i]) {
|
||||
throw new AssertionError("Decompressed and original differ at index " + i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isIncluded(List<Pattern> includesPatterns, String path) {
|
||||
return includesPatterns.isEmpty() ||
|
||||
includesPatterns.stream().anyMatch((pattern) -> pattern.matcher(path).matches());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue