From 7f2d5315fd85fda43b5a5e7040c3a47a68a052b5 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sun, 28 Apr 2024 13:14:10 -0700 Subject: [PATCH] Rewrite reflection in library loader jars (#10608) * Rewrite reflection in library loader jars * Address todos --- ...difying-library-loader-jars-bytecode.patch | 34 +++ ...-Modify-library-loader-jars-bytecode.patch | 261 ++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 patches/api/0473-Allow-modifying-library-loader-jars-bytecode.patch create mode 100644 patches/server/1046-Modify-library-loader-jars-bytecode.patch 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 <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 repositories; ++ public static java.util.function.BiFunction 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 <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 modifier; ++ private final Map manifests = new ConcurrentHashMap<>(); ++ ++ public BytecodeModifyingURLClassLoader( ++ final URL[] urls, ++ final ClassLoader parent, ++ final Function 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 { + ++ static { ++ LibraryLoader.LIBRARY_LOADER_FACTORY = BytecodeModifyingURLClassLoader::new; ++ } ++ + @Override + public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException { + // Copied from SimplePluginManager#loadPlugins