diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java index 974fc1bd..785ad2ee 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java @@ -20,7 +20,6 @@ package com.sk89q.worldguard.bukkit; import com.google.common.collect.ImmutableList; -import com.sk89q.bukkit.util.ClassSourceValidator; import com.sk89q.bukkit.util.CommandsManagerRegistration; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissionsException; @@ -60,6 +59,7 @@ import com.sk89q.worldguard.bukkit.listener.WorldGuardWorldListener; import com.sk89q.worldguard.bukkit.listener.WorldRulesListener; import com.sk89q.worldguard.bukkit.session.BukkitSessionManager; +import com.sk89q.worldguard.bukkit.util.ClassSourceValidator; import com.sk89q.worldguard.bukkit.util.Events; import com.sk89q.worldguard.commands.GeneralCommands; import com.sk89q.worldguard.commands.ProtectionCommands; @@ -71,7 +71,6 @@ import com.sk89q.worldguard.protection.managers.storage.RegionDriver; import com.sk89q.worldguard.protection.managers.storage.file.DirectoryYamlDriver; import com.sk89q.worldguard.protection.managers.storage.sql.SQLDriver; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.util.logging.RecordMessagePrefixer; import org.bstats.bukkit.Metrics; @@ -137,6 +136,10 @@ public static WorldGuardPlugin inst() { */ @Override public void onEnable() { + // Catch bad things being done by naughty plugins that include WorldGuard's classes + ClassSourceValidator verifier = new ClassSourceValidator(this); + verifier.reportMismatches(ImmutableList.of(WorldGuard.class, ProtectedRegion.class, Flag.class)); + configureLogger(); getDataFolder().mkdirs(); // Need to create the plugins/WorldGuard folder @@ -150,13 +153,6 @@ public void onEnable() { // Set the proper command injector commands.setInjector(new SimpleInjector(WorldGuard.getInstance())); - // Catch bad things being done by naughty plugins that include WorldGuard's classes - try { - ClassSourceValidator verifier = new ClassSourceValidator(this); - verifier.reportMismatches(ImmutableList.of(WorldGuard.class, ProtectedRegion.class, Flag.class)); - } catch (NoClassDefFoundError ignored) { // was added to WE two years ago but who knows - } - // Register command classes final CommandsManagerRegistration reg = new CommandsManagerRegistration(this, commands); reg.register(ToggleCommands.class); diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/ClassSourceValidator.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/ClassSourceValidator.java new file mode 100644 index 00000000..a9454c47 --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/ClassSourceValidator.java @@ -0,0 +1,164 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.PluginClassLoader; + +import javax.annotation.Nullable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Validates that certain specified classes came from the same source as + * a plugin. + * This is copied from the same class in WorldEdit because unfortunately + * trying to use WorldEdit's means we're susceptible to getting a bad version + * of this class if another plugin shades it....which is exactly what we're + * trying to detect and report. + */ +public class ClassSourceValidator { + + private static final String SEPARATOR_LINE = Strings.repeat("*", 46); + private static final Method loadClass; + + static { + Method tmp; + try { + tmp = PluginClassLoader.class.getDeclaredMethod("loadClass0", + String.class, boolean.class, boolean.class, boolean.class); + tmp.setAccessible(true); + } catch (NoSuchMethodException e) { + tmp = null; + } + loadClass = tmp; + } + + private final Plugin plugin; + @Nullable + private final ClassLoader expectedClassLoader; + + /** + * Create a new instance. + * + * @param plugin The plugin + */ + public ClassSourceValidator(Plugin plugin) { + checkNotNull(plugin, "plugin"); + this.plugin = plugin; + this.expectedClassLoader = plugin.getClass().getClassLoader(); + } + + /** + * Return a map of classes that been loaded from a different source. + * + * @param classes A list of classes to check + * @return The results + */ + public Map, Plugin> findMismatches(List> classes) { + checkNotNull(classes, "classes"); + + if (expectedClassLoader == null || loadClass == null) { + return ImmutableMap.of(); + } + + Map, Plugin> mismatches = new HashMap<>(); + + for (Plugin target : Bukkit.getPluginManager().getPlugins()) { + if (target == plugin) { + continue; + } + ClassLoader targetLoader = target.getClass().getClassLoader(); + if (!(targetLoader instanceof PluginClassLoader)) { + continue; + } + for (Class testClass : classes) { + Class targetClass; + try { + targetClass = (Class) loadClass.invoke(targetLoader, testClass.getName(), false, false, false); + } catch (IllegalAccessException | InvocationTargetException ignored) { + continue; + } + if (targetClass.getClassLoader() != expectedClassLoader) { + mismatches.putIfAbsent(testClass, targetClass.getClassLoader() == targetLoader ? target : null); + } + } + } + + return mismatches; + } + + /** + * Reports classes that have come from a different source. + * + *

The warning is emitted to the log.

+ * + * @param classes The list of classes to check + */ + public void reportMismatches(List> classes) { + if (Boolean.getBoolean("enginehub.disable.class.source.validation")) { + return; + } + Map, Plugin> mismatches = findMismatches(classes); + + if (mismatches.isEmpty()) { + return; + } + StringBuilder builder = new StringBuilder("\n"); + + builder.append(SEPARATOR_LINE).append("\n"); + builder.append("** /!\\ SEVERE WARNING /!\\\n"); + builder.append("** \n"); + builder.append("** A plugin developer has included a portion of \n"); + builder.append("** ").append(plugin.getName()).append(" into their own plugin, so rather than using\n"); + builder.append("** the version of ").append(plugin.getName()).append(" that you downloaded, you\n"); + builder.append("** will be using a broken mix of old ").append(plugin.getName()).append(" (that came\n"); + builder.append("** with the plugin) and your downloaded version. THIS MAY\n"); + builder.append("** SEVERELY BREAK ").append(plugin.getName().toUpperCase(Locale.ROOT)).append(" AND ALL OF ITS FEATURES.\n"); + builder.append("**\n"); + builder.append("** This may have happened because the developer is using\n"); + builder.append("** the ").append(plugin.getName()).append(" API and thinks that including\n"); + builder.append("** ").append(plugin.getName()).append(" is necessary. However, it is not!\n"); + builder.append("**\n"); + builder.append("** Here are some files that have been overridden:\n"); + builder.append("** \n"); + for (Map.Entry, Plugin> entry : mismatches.entrySet()) { + Plugin badPlugin = entry.getValue(); + String url = badPlugin == null + ? "(unknown)" + : badPlugin.getName() + " (" + badPlugin.getClass().getProtectionDomain().getCodeSource().getLocation() + ")"; + builder.append("** '").append(entry.getKey().getSimpleName()).append("' came from '").append(url).append("'\n"); + } + builder.append("**\n"); + builder.append("** Please report this to the plugins' developers.\n"); + builder.append(SEPARATOR_LINE).append("\n"); + + plugin.getLogger().severe(builder.toString()); + } +}