From fffcd7f2bb87638e2320d3b2f5ea8ec066f4e9b5 Mon Sep 17 00:00:00 2001 From: ~keith Date: Thu, 20 Jan 2022 20:24:44 +0000 Subject: [PATCH] Reject unpatched clients on patched server & warn about unpatched servers --- .../fml/client/ClientHooks.java | 17 +++++++++++++++-- .../fml/network/FMLNetworkConstants.java | 3 ++- .../fml/network/FMLStatusPing.java | 18 +++++++++++++++++- .../fml/server/ServerLifecycleHooks.java | 6 ++++++ .../resources/assets/forge/lang/en_us.json | 1 + .../assets/forge/textures/gui/icons.png | Bin 10289 -> 10789 bytes 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/client/ClientHooks.java b/src/main/java/net/minecraftforge/fml/client/ClientHooks.java index ea796b0d3..0558145c1 100644 --- a/src/main/java/net/minecraftforge/fml/client/ClientHooks.java +++ b/src/main/java/net/minecraftforge/fml/client/ClientHooks.java @@ -128,6 +128,11 @@ public class ClientHooks if (fmlver > FMLNetworkConstants.FMLNETVERSION) { extraReason = "fml.menu.multiplayer.clientoutdated"; } + + if (!packet.getForgeData().isPatchAdvertised()) { + extraReason = "fml.menu.multiplayer.serverunpatched"; + } + target.forgeData = new ExtendedServerListData("FML", extraServerMods.isEmpty() && fmlNetMatches && channelsMatch && modsMatch, mods.size(), extraReason); } else { target.forgeData = new ExtendedServerListData("VANILLA", NetworkRegistry.canConnectToVanillaServer(),0, null); @@ -143,11 +148,19 @@ public class ClientHooks switch (target.forgeData.type) { case "FML": if (target.forgeData.isCompatible) { - idx = 0; - tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.compatible", target.forgeData.numberOfMods); + // HACK: Allow connections to unpatched servers, but show a warning + if (target.forgeData.extraReason != null && target.forgeData.extraReason.equals("fml.menu.multiplayer.serverunpatched")) { + idx = 96; + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.incompatible.extra", ForgeI18n.parseMessage(target.forgeData.extraReason)); + } else { + idx = 0; + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.compatible", target.forgeData.numberOfMods); + } } else { idx = 16; if(target.forgeData.extraReason != null) { + if (target.forgeData.extraReason.equals("fml.menu.multiplayer.serverunpatched")) + idx = 96; String extraReason = ForgeI18n.parseMessage(target.forgeData.extraReason); tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.incompatible.extra", extraReason); } else { diff --git a/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java b/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java index 897f5ed86..8c50bf4ea 100644 --- a/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java +++ b/src/main/java/net/minecraftforge/fml/network/FMLNetworkConstants.java @@ -34,7 +34,8 @@ public class FMLNetworkConstants { public static final String FMLNETMARKER = "FML"; public static final int FMLNETVERSION = 2; - public static final String NETVERSION = FMLNETMARKER + FMLNETVERSION; + // HACK: Unpatched servers won't see this leading 0 - but we will. + public static final String NETVERSION = FMLNETMARKER + "0" + FMLNETVERSION; public static final String NOVERSION = "NONE"; static final Marker NETWORK = MarkerManager.getMarker("FMLNETWORK"); diff --git a/src/main/java/net/minecraftforge/fml/network/FMLStatusPing.java b/src/main/java/net/minecraftforge/fml/network/FMLStatusPing.java index ad59bb4a9..f6e849b1f 100644 --- a/src/main/java/net/minecraftforge/fml/network/FMLStatusPing.java +++ b/src/main/java/net/minecraftforge/fml/network/FMLStatusPing.java @@ -67,6 +67,7 @@ public class FMLStatusPing { private transient Map> channels; private transient Map mods; private transient int fmlNetworkVer; + private transient boolean patched; public FMLStatusPing() { this.channels = NetworkRegistry.buildChannelVersionsForListPing(); this.mods = new HashMap<>(); @@ -74,12 +75,20 @@ public class FMLStatusPing { mods.put(modid, mc.getCustomExtension(ExtensionPoint.DISPLAYTEST). map(Pair::getLeft).map(Supplier::get).orElse(FMLNetworkConstants.IGNORESERVERONLY))); this.fmlNetworkVer = FMLNetworkConstants.FMLNETVERSION; + this.patched = true; } private FMLStatusPing(Map> deserialized, Map modMarkers, int fmlNetVer) { this.channels = ImmutableMap.copyOf(deserialized); this.mods = modMarkers; this.fmlNetworkVer = fmlNetVer; + this.patched = false; + } + private FMLStatusPing(Map> deserialized, Map modMarkers, int fmlNetVer, boolean p) { + this.channels = ImmutableMap.copyOf(deserialized); + this.mods = modMarkers; + this.fmlNetworkVer = fmlNetVer; + this.patched = p; } public static class Serializer { @@ -96,7 +105,8 @@ public class FMLStatusPing { collect(Collectors.toMap(jo -> JSONUtils.getString(jo, "modId"), jo->JSONUtils.getString(jo, "modmarker"))); final int remoteFMLVersion = JSONUtils.getInt(forgeData, "fmlNetworkVersion"); - return new FMLStatusPing(channels, mods, remoteFMLVersion); + + return new FMLStatusPing(channels, mods, remoteFMLVersion, JSONUtils.hasField(forgeData, "keithPatch")); } catch (JsonSyntaxException e) { LOGGER.debug(NETWORK, "Encountered an error parsing status ping data", e); return null; @@ -125,6 +135,8 @@ public class FMLStatusPing { }); obj.add("mods", modTestValues); obj.addProperty("fmlNetworkVersion", forgeData.fmlNetworkVer); + if (forgeData.patched) + obj.addProperty("keithPatch", true); return obj; } } @@ -141,4 +153,8 @@ public class FMLStatusPing { return fmlNetworkVer; } + public boolean isPatchAdvertised() { + return patched; + } + } diff --git a/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java b/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java index 8fe006c3d..683c776eb 100644 --- a/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java +++ b/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java @@ -155,6 +155,12 @@ public class ServerLifecycleHooks final ConnectionType connectionType = ConnectionType.forVersionFlag(packet.getFMLVersion()); final int versionNumber = connectionType.getFMLVersionNumber(packet.getFMLVersion()); + // HACK: Check for missing leading 0. If it's not there, we know the client isn't patched. + if (connectionType == ConnectionType.MODDED && packet.getFMLVersion().substring(FMLNetworkConstants.FMLNETMARKER.length()).charAt(0) != '0') { + rejectConnection(manager, connectionType, "Your client appears to be vulnerable to CVE-2021-44228, 45046, 45105, and/or 44832. To connect to this server, you must install ~keith's patched Forge version: https://bytes.keithhacks.cyou/keith/ForgePatch/releases/"); + return false; + } + if (connectionType == ConnectionType.MODDED && versionNumber != FMLNetworkConstants.FMLNETVERSION) { rejectConnection(manager, connectionType, "This modded server is not network compatible with your modded client. Please verify your Forge version closely matches the server. Got net version "+ versionNumber + " this server is net version "+FMLNetworkConstants.FMLNETVERSION); return false; diff --git a/src/main/resources/assets/forge/lang/en_us.json b/src/main/resources/assets/forge/lang/en_us.json index 0f72f24bd..ef4b620f7 100644 --- a/src/main/resources/assets/forge/lang/en_us.json +++ b/src/main/resources/assets/forge/lang/en_us.json @@ -34,6 +34,7 @@ "fml.menu.multiplayer.extraservermods":"Server has additional mods that may be needed on the client", "fml.menu.multiplayer.modsincompatible":"Server mod list is not compatible", "fml.menu.multiplayer.networkincompatible":"Server network message list is not compatible", + "fml.menu.multiplayer.serverunpatched": "Server is unpatched, or not advertising keith/ForgePatch", "fml.menu.loadingmods": "{0,choice,0#No mods|1#1 mod|1<{0} mods} loaded", "fml.menu.notification.title": "Startup Notification", "fml.menu.accessdenied.title": "Server Access Denied", diff --git a/src/main/resources/assets/forge/textures/gui/icons.png b/src/main/resources/assets/forge/textures/gui/icons.png index 846844a7b72f2a29804675e00f73b445a6740244..4a7bac838e2f61a241d7c6689d8f6d16b73bfb66 100644 GIT binary patch literal 10789 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�wKKd-5^zWJi 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 URVlvAOZov|IaS#zY2(oU11_CEZvX%Q