aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJason Penilla <[email protected]>2024-04-28 13:14:10 -0700
committerGitHub <[email protected]>2024-04-28 13:14:10 -0700
commit7f2d5315fd85fda43b5a5e7040c3a47a68a052b5 (patch)
tree58419cbb32b60343730c01920314dbcb5e4ee866
parent61c9c07e95d84ce6dcdf92d02968cfa9f3efe868 (diff)
downloadPaper-7f2d5315fd85fda43b5a5e7040c3a47a68a052b5.tar.gz
Paper-7f2d5315fd85fda43b5a5e7040c3a47a68a052b5.zip
Rewrite reflection in library loader jars (#10608)
* Rewrite reflection in library loader jars * Address todos
-rw-r--r--patches/api/0473-Allow-modifying-library-loader-jars-bytecode.patch34
-rw-r--r--patches/server/1046-Modify-library-loader-jars-bytecode.patch261
2 files changed, 295 insertions, 0 deletions
diff --git a/patches/api/0473-Allow-modifying-library-loader-jars-bytecode.patch b/patches/api/0473-Allow-modifying-library-loader-jars-bytecode.patch
new file mode 100644
index 0000000000..0c0311e790
--- /dev/null
+++ b/patches/api/0473-Allow-modifying-library-loader-jars-bytecode.patch
@@ -0,0 +1,34 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+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;
+ }
diff --git a/patches/server/1046-Modify-library-loader-jars-bytecode.patch b/patches/server/1046-Modify-library-loader-jars-bytecode.patch
new file mode 100644
index 0000000000..126560a15e
--- /dev/null
+++ b/patches/server/1046-Modify-library-loader-jars-bytecode.patch
@@ -0,0 +1,261 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+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