mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-25 20:16:19 +01:00
Rewrite reflection in library loader jars (#10608)
* Rewrite reflection in library loader jars * Address todos
This commit is contained in:
parent
61c9c07e95
commit
7f2d5315fd
@ -0,0 +1,34 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
|
||||||
|
Date: Sun, 28 Apr 2024 11:11:26 -0700
|
||||||
|
Subject: [PATCH] Allow modifying library loader jars bytecode
|
||||||
|
|
||||||
|
|
||||||
|
diff --git a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
|
||||||
|
index f4d655a158410039305ac68cebe0d79000f73df8..5b0203e908f84c531886b8ea8faeb591eb045636 100644
|
||||||
|
--- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
|
||||||
|
+++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
|
||||||
|
@@ -46,6 +46,7 @@ public class LibraryLoader
|
||||||
|
private final RepositorySystem repository;
|
||||||
|
private final DefaultRepositorySystemSession session;
|
||||||
|
private final List<RemoteRepository> repositories;
|
||||||
|
+ public static java.util.function.BiFunction<URL[], ClassLoader, URLClassLoader> LIBRARY_LOADER_FACTORY; // Paper - rewrite reflection in libraries
|
||||||
|
|
||||||
|
public LibraryLoader(@NotNull Logger logger)
|
||||||
|
{
|
||||||
|
@@ -130,7 +131,14 @@ public class LibraryLoader
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
- URLClassLoader loader = new URLClassLoader( jarFiles.toArray( new URL[ jarFiles.size() ] ), getClass().getClassLoader() );
|
||||||
|
+ // Paper start - rewrite reflection in libraries
|
||||||
|
+ URLClassLoader loader;
|
||||||
|
+ if (LIBRARY_LOADER_FACTORY == null) {
|
||||||
|
+ loader = new URLClassLoader( jarFiles.toArray( new URL[ jarFiles.size() ] ), getClass().getClassLoader() );
|
||||||
|
+ } else {
|
||||||
|
+ loader = LIBRARY_LOADER_FACTORY.apply(jarFiles.toArray( new URL[ jarFiles.size() ] ), getClass().getClassLoader());
|
||||||
|
+ }
|
||||||
|
+ // Paper end - rewrite reflection in libraries
|
||||||
|
|
||||||
|
return loader;
|
||||||
|
}
|
261
patches/server/1046-Modify-library-loader-jars-bytecode.patch
Normal file
261
patches/server/1046-Modify-library-loader-jars-bytecode.patch
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
|
||||||
|
Date: Sun, 28 Apr 2024 11:12:14 -0700
|
||||||
|
Subject: [PATCH] Modify library loader jars bytecode
|
||||||
|
|
||||||
|
|
||||||
|
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000000000000000000000000000000000000..405416dc3d1c8c58b4e0c880d8751ca319188f62
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java
|
||||||
|
@@ -0,0 +1,185 @@
|
||||||
|
+package io.papermc.paper.plugin.entrypoint.classloader;
|
||||||
|
+
|
||||||
|
+import io.papermc.paper.pluginremap.reflect.ReflectionRemapper;
|
||||||
|
+import java.io.IOException;
|
||||||
|
+import java.io.InputStream;
|
||||||
|
+import java.io.UncheckedIOException;
|
||||||
|
+import java.net.JarURLConnection;
|
||||||
|
+import java.net.URL;
|
||||||
|
+import java.net.URLClassLoader;
|
||||||
|
+import java.security.CodeSigner;
|
||||||
|
+import java.security.CodeSource;
|
||||||
|
+import java.util.Map;
|
||||||
|
+import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
+import java.util.function.Function;
|
||||||
|
+import java.util.jar.Attributes;
|
||||||
|
+import java.util.jar.Manifest;
|
||||||
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
+import org.objectweb.asm.ClassReader;
|
||||||
|
+import org.objectweb.asm.ClassVisitor;
|
||||||
|
+import org.objectweb.asm.ClassWriter;
|
||||||
|
+
|
||||||
|
+import static java.util.Objects.requireNonNullElse;
|
||||||
|
+
|
||||||
|
+public final class BytecodeModifyingURLClassLoader extends URLClassLoader {
|
||||||
|
+ static {
|
||||||
|
+ ClassLoader.registerAsParallelCapable();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private static final Object MISSING_MANIFEST = new Object();
|
||||||
|
+
|
||||||
|
+ private final Function<byte[], byte[]> modifier;
|
||||||
|
+ private final Map<String, Object> manifests = new ConcurrentHashMap<>();
|
||||||
|
+
|
||||||
|
+ public BytecodeModifyingURLClassLoader(
|
||||||
|
+ final URL[] urls,
|
||||||
|
+ final ClassLoader parent,
|
||||||
|
+ final Function<byte[], byte[]> modifier
|
||||||
|
+ ) {
|
||||||
|
+ super(urls, parent);
|
||||||
|
+ this.modifier = modifier;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public BytecodeModifyingURLClassLoader(
|
||||||
|
+ final URL[] urls,
|
||||||
|
+ final ClassLoader parent
|
||||||
|
+ ) {
|
||||||
|
+ this(urls, parent, bytes -> {
|
||||||
|
+ final ClassReader classReader = new ClassReader(bytes);
|
||||||
|
+ final ClassWriter classWriter = new ClassWriter(classReader, 0);
|
||||||
|
+ final ClassVisitor visitor = ReflectionRemapper.visitor(classWriter);
|
||||||
|
+ if (visitor == classWriter) {
|
||||||
|
+ return bytes;
|
||||||
|
+ }
|
||||||
|
+ classReader.accept(visitor, 0);
|
||||||
|
+ return classWriter.toByteArray();
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ @Override
|
||||||
|
+ protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
||||||
|
+ final Class<?> result;
|
||||||
|
+ final String path = name.replace('.', '/').concat(".class");
|
||||||
|
+ final URL url = this.findResource(path);
|
||||||
|
+ if (url != null) {
|
||||||
|
+ try {
|
||||||
|
+ result = this.defineClass(name, url);
|
||||||
|
+ } catch (final IOException e) {
|
||||||
|
+ throw new ClassNotFoundException(name, e);
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ result = null;
|
||||||
|
+ }
|
||||||
|
+ if (result == null) {
|
||||||
|
+ throw new ClassNotFoundException(name);
|
||||||
|
+ }
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private Class<?> defineClass(String name, URL url) throws IOException {
|
||||||
|
+ int i = name.lastIndexOf('.');
|
||||||
|
+ if (i != -1) {
|
||||||
|
+ String pkgname = name.substring(0, i);
|
||||||
|
+ // Check if package already loaded.
|
||||||
|
+ final @Nullable Manifest man = this.manifestFor(url);
|
||||||
|
+ if (this.getAndVerifyPackage(pkgname, man, url) == null) {
|
||||||
|
+ try {
|
||||||
|
+ if (man != null) {
|
||||||
|
+ this.definePackage(pkgname, man, url);
|
||||||
|
+ } else {
|
||||||
|
+ this.definePackage(pkgname, null, null, null, null, null, null, null);
|
||||||
|
+ }
|
||||||
|
+ } catch (IllegalArgumentException iae) {
|
||||||
|
+ // parallel-capable class loaders: re-verify in case of a
|
||||||
|
+ // race condition
|
||||||
|
+ if (this.getAndVerifyPackage(pkgname, man, url) == null) {
|
||||||
|
+ // Should never happen
|
||||||
|
+ throw new AssertionError("Cannot find package " +
|
||||||
|
+ pkgname);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ final byte[] bytes;
|
||||||
|
+ try (final InputStream is = url.openStream()) {
|
||||||
|
+ bytes = is.readAllBytes();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ final byte[] modified = this.modifier.apply(bytes);
|
||||||
|
+
|
||||||
|
+ final CodeSource cs = new CodeSource(url, (CodeSigner[]) null);
|
||||||
|
+ return this.defineClass(name, modified, 0, modified.length, cs);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private Package getAndVerifyPackage(
|
||||||
|
+ String pkgname,
|
||||||
|
+ Manifest man, URL url
|
||||||
|
+ ) {
|
||||||
|
+ Package pkg = getDefinedPackage(pkgname);
|
||||||
|
+ if (pkg != null) {
|
||||||
|
+ // Package found, so check package sealing.
|
||||||
|
+ if (pkg.isSealed()) {
|
||||||
|
+ // Verify that code source URL is the same.
|
||||||
|
+ if (!pkg.isSealed(url)) {
|
||||||
|
+ throw new SecurityException(
|
||||||
|
+ "sealing violation: package " + pkgname + " is sealed");
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ // Make sure we are not attempting to seal the package
|
||||||
|
+ // at this code source URL.
|
||||||
|
+ if ((man != null) && this.isSealed(pkgname, man)) {
|
||||||
|
+ throw new SecurityException(
|
||||||
|
+ "sealing violation: can't seal package " + pkgname +
|
||||||
|
+ ": already loaded");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return pkg;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private boolean isSealed(String name, Manifest man) {
|
||||||
|
+ Attributes attr = man.getAttributes(name.replace('.', '/').concat("/"));
|
||||||
|
+ String sealed = null;
|
||||||
|
+ if (attr != null) {
|
||||||
|
+ sealed = attr.getValue(Attributes.Name.SEALED);
|
||||||
|
+ }
|
||||||
|
+ if (sealed == null) {
|
||||||
|
+ if ((attr = man.getMainAttributes()) != null) {
|
||||||
|
+ sealed = attr.getValue(Attributes.Name.SEALED);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return "true".equalsIgnoreCase(sealed);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private @Nullable Manifest manifestFor(final URL url) throws IOException {
|
||||||
|
+ Manifest man = null;
|
||||||
|
+ if (url.getProtocol().equals("jar")) {
|
||||||
|
+ try {
|
||||||
|
+ final Object computedManifest = this.manifests.computeIfAbsent(jarName(url), $ -> {
|
||||||
|
+ try {
|
||||||
|
+ final Manifest m = ((JarURLConnection) url.openConnection()).getManifest();
|
||||||
|
+ return requireNonNullElse(m, MISSING_MANIFEST);
|
||||||
|
+ } catch (final IOException e) {
|
||||||
|
+ throw new UncheckedIOException(e);
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+ if (computedManifest instanceof Manifest found) {
|
||||||
|
+ man = found;
|
||||||
|
+ }
|
||||||
|
+ } catch (final UncheckedIOException e) {
|
||||||
|
+ throw e.getCause();
|
||||||
|
+ } catch (final IllegalArgumentException e) {
|
||||||
|
+ throw new IOException(e);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return man;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private static String jarName(final URL sourceUrl) {
|
||||||
|
+ final int exclamationIdx = sourceUrl.getPath().lastIndexOf('!');
|
||||||
|
+ if (exclamationIdx != -1) {
|
||||||
|
+ return sourceUrl.getPath().substring(0, exclamationIdx);
|
||||||
|
+ }
|
||||||
|
+ throw new IllegalArgumentException("Could not find jar for URL " + sourceUrl);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
|
||||||
|
index f38ecd7f65dc24e4a3f0bc675e3730287ac353f1..ca6cb891e9da9d7e08f1a82fab212d2063cc9ef6 100644
|
||||||
|
--- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
|
||||||
|
+++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
|
||||||
|
@@ -1,12 +1,11 @@
|
||||||
|
package io.papermc.paper.plugin.loader;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
|
||||||
|
+import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader;
|
||||||
|
+import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
||||||
|
import io.papermc.paper.plugin.loader.library.ClassPathLibrary;
|
||||||
|
import io.papermc.paper.plugin.loader.library.PaperLibraryStore;
|
||||||
|
-import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
||||||
|
import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||||||
|
-import org.jetbrains.annotations.NotNull;
|
||||||
|
-
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
@@ -16,6 +15,7 @@ import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
+import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class PaperClasspathBuilder implements PluginClasspathBuilder {
|
||||||
|
|
||||||
|
@@ -56,7 +56,8 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
- return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader()));
|
||||||
|
+ final URLClassLoader libraryLoader = new BytecodeModifyingURLClassLoader(urls, this.getClass().getClassLoader());
|
||||||
|
+ return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), libraryLoader);
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
|
||||||
|
index bdd9bc8a414719b9f1d6f01f90539ddb8603a878..31f05a7336ea124d24a5059652a2950a9f672758 100644
|
||||||
|
--- a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
|
||||||
|
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
|
||||||
|
@@ -1,9 +1,11 @@
|
||||||
|
package io.papermc.paper.plugin.provider.type.spigot;
|
||||||
|
|
||||||
|
+import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader;
|
||||||
|
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
|
||||||
|
import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
|
||||||
|
import org.bukkit.plugin.InvalidDescriptionException;
|
||||||
|
import org.bukkit.plugin.PluginDescriptionFile;
|
||||||
|
+import org.bukkit.plugin.java.LibraryLoader;
|
||||||
|
import org.yaml.snakeyaml.error.YAMLException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
@@ -15,6 +17,10 @@ import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> {
|
||||||
|
|
||||||
|
+ static {
|
||||||
|
+ LibraryLoader.LIBRARY_LOADER_FACTORY = BytecodeModifyingURLClassLoader::new;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
@Override
|
||||||
|
public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException {
|
||||||
|
// Copied from SimplePluginManager#loadPlugins
|
Loading…
Reference in New Issue
Block a user