Fix signed jar class caching (#241)

This commit is contained in:
IzzelAliz 2021-04-24 13:27:32 +08:00
parent 0e02e12b3f
commit d9370b4f65
2 changed files with 50 additions and 15 deletions

View File

@ -6,6 +6,7 @@ import io.izzel.arclight.common.bridge.bukkit.JavaPluginLoaderBridge;
import io.izzel.arclight.common.mod.util.remapper.ArclightRemapper; import io.izzel.arclight.common.mod.util.remapper.ArclightRemapper;
import io.izzel.arclight.common.mod.util.remapper.ClassLoaderRemapper; import io.izzel.arclight.common.mod.util.remapper.ClassLoaderRemapper;
import io.izzel.arclight.common.mod.util.remapper.RemappingClassLoader; import io.izzel.arclight.common.mod.util.remapper.RemappingClassLoader;
import io.izzel.tools.product.Product2;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPluginLoader; import org.bukkit.plugin.java.JavaPluginLoader;
@ -16,11 +17,9 @@ import org.spongepowered.asm.mixin.Shadow;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.net.URLConnection; import java.net.URLConnection;
import java.security.CodeSigner;
import java.security.CodeSource; import java.security.CodeSource;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -74,16 +73,10 @@ public class PluginClassLoaderMixin extends URLClassLoader implements RemappingC
if (url != null) { if (url != null) {
URLConnection connection; URLConnection connection;
CodeSigner[] signers;
Callable<byte[]> byteSource; Callable<byte[]> byteSource;
try { try {
connection = url.openConnection(); connection = url.openConnection();
connection.connect(); connection.connect();
if (connection instanceof JarURLConnection) {
signers = ((JarURLConnection) connection).getJarEntry().getCodeSigners();
} else {
signers = new CodeSigner[0];
}
byteSource = () -> { byteSource = () -> {
try (InputStream is = connection.getInputStream()) { try (InputStream is = connection.getInputStream()) {
byte[] classBytes = ByteStreams.toByteArray(is); byte[] classBytes = ByteStreams.toByteArray(is);
@ -96,7 +89,7 @@ public class PluginClassLoaderMixin extends URLClassLoader implements RemappingC
throw new ClassNotFoundException(name, e); throw new ClassNotFoundException(name, e);
} }
byte[] classBytes = this.getRemapper().remapClass(name, byteSource, connection); Product2<byte[], CodeSource> classBytes = this.getRemapper().remapClass(name, byteSource, connection);
int dot = name.lastIndexOf('.'); int dot = name.lastIndexOf('.');
if (dot != -1) { if (dot != -1) {
@ -116,9 +109,7 @@ public class PluginClassLoaderMixin extends URLClassLoader implements RemappingC
} }
} }
CodeSource source = new CodeSource(this.url, signers); result = defineClass(name, classBytes._1, 0, classBytes._1.length, classBytes._2);
result = defineClass(name, classBytes, 0, classBytes.length, source);
} }
if (result == null) { if (result == null) {

View File

@ -4,9 +4,12 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.api.Unsafe;
import io.izzel.arclight.common.mod.util.remapper.generated.ArclightReflectionHandler; import io.izzel.arclight.common.mod.util.remapper.generated.ArclightReflectionHandler;
import io.izzel.arclight.i18n.ArclightConfig; import io.izzel.arclight.i18n.ArclightConfig;
import io.izzel.tools.product.Product;
import io.izzel.tools.product.Product2;
import net.md_5.specialsource.JarMapping; import net.md_5.specialsource.JarMapping;
import net.md_5.specialsource.JarRemapper; import net.md_5.specialsource.JarRemapper;
import net.md_5.specialsource.RemappingClassAdapter; import net.md_5.specialsource.RemappingClassAdapter;
@ -28,8 +31,12 @@ import java.io.File;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.file.Files; import java.nio.file.Files;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -39,6 +46,7 @@ import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarFile;
public class ClassLoaderRemapper extends LenientJarRemapper { public class ClassLoaderRemapper extends LenientJarRemapper {
@ -52,6 +60,7 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
private final String generatedHandler; private final String generatedHandler;
private final Class<?> generatedHandlerClass; private final Class<?> generatedHandlerClass;
private final GeneratedHandlerAdapter generatedHandlerAdapter; private final GeneratedHandlerAdapter generatedHandlerAdapter;
private final Map<String, Boolean> secureJarInfo = new ConcurrentHashMap<>();
public ClassLoaderRemapper(JarMapping jarMapping, JarMapping toBukkitMapping, ClassLoader classLoader) { public ClassLoaderRemapper(JarMapping jarMapping, JarMapping toBukkitMapping, ClassLoader classLoader) {
super(jarMapping); super(jarMapping);
@ -279,7 +288,19 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
return Maps.immutableEntry(owner, mapped); return Maps.immutableEntry(owner, mapped);
} }
public byte[] remapClass(String className, Callable<byte[]> byteSource, URLConnection connection) throws ClassNotFoundException { private boolean isSecureJar(JarFile jarFile) {
return this.secureJarInfo.computeIfAbsent(jarFile.getName(), key ->
jarFile.stream().anyMatch(it -> {
if (it.isDirectory()) return false;
String name = it.getName();
return name.startsWith("META-INF") && (name.endsWith(".DSA") ||
name.endsWith(".RSA") ||
name.endsWith(".EC") ||
name.endsWith(".SF"));
}));
}
public Product2<byte[], CodeSource> remapClass(String className, Callable<byte[]> byteSource, URLConnection connection) throws ClassNotFoundException {
try { try {
ArclightClassCache.CacheSegment segment = ArclightClassCache.instance().makeSegment(connection); ArclightClassCache.CacheSegment segment = ArclightClassCache.instance().makeSegment(connection);
Optional<byte[]> optional = segment.findByName(className); Optional<byte[]> optional = segment.findByName(className);
@ -287,7 +308,21 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
byte[] bytes = optional.get(); byte[] bytes = optional.get();
ClassWriter cw = new ClassWriter(0); ClassWriter cw = new ClassWriter(0);
new ClassReader(bytes).accept(new ClassRemapper(cw, generatedHandlerAdapter), 0); new ClassReader(bytes).accept(new ClassRemapper(cw, generatedHandlerAdapter), 0);
return cw.toByteArray(); URL url;
CodeSigner[] signers;
if (connection instanceof JarURLConnection) {
url = ((JarURLConnection) connection).getJarFileURL();
if (isSecureJar(((JarURLConnection) connection).getJarFile())) {
ByteStreams.exhaust(connection.getInputStream()); // must read before asking signers
signers = ((JarURLConnection) connection).getJarEntry().getCodeSigners();
} else {
signers = null;
}
} else {
url = connection.getURL();
signers = null;
}
return Product.of(cw.toByteArray(), new CodeSource(url, signers));
} else { } else {
byte[] bytes = remapClassFile(byteSource.call(), GlobalClassRepo.INSTANCE); byte[] bytes = remapClassFile(byteSource.call(), GlobalClassRepo.INSTANCE);
if (ArclightConfig.spec().getOptimization().isCachePluginClass()) { if (ArclightConfig.spec().getOptimization().isCachePluginClass()) {
@ -296,7 +331,16 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
byte[] store = cw.toByteArray(); byte[] store = cw.toByteArray();
segment.addToCache(className, store); segment.addToCache(className, store);
} }
return bytes; URL url;
CodeSigner[] signers;
if (connection instanceof JarURLConnection) {
url = ((JarURLConnection) connection).getJarFileURL();
signers = ((JarURLConnection) connection).getJarEntry().getCodeSigners();
} else {
url = connection.getURL();
signers = null;
}
return Product.of(bytes, new CodeSource(url, signers));
} }
} catch (Exception e) { } catch (Exception e) {
throw new ClassNotFoundException(className, e); throw new ClassNotFoundException(className, e);