From cbfc1beb6a135593c51a1ad2f51d792731fa9661 Mon Sep 17 00:00:00 2001 From: Luna <90072930+LunaWasFlaggedAgain@users.noreply.github.com> Date: Wed, 31 May 2023 20:18:11 -0300 Subject: [PATCH] Add depth limit to Component deserializer (1.19.4) (#84) * Add depth limit to Component deserializer * Make depth limit configurable; increase placeholder penalty --- ...ch-invalid-entity-rotation-log-spam.patch} | 0 ...ble-components-with-more-than-32-pla.patch | 102 ----------- ...Patch-large-selector-distance-crash.patch} | 0 ...mit-sculk-catalyst-cursor-positions.patch} | 0 ...41-Limit-map-decoration-text-length.patch} | 0 ... => 0042-Limit-map-decoration-count.patch} | 0 ...layer-banning-using-duplicate-UUIDs.patch} | 2 +- ...on-t-warn-on-duplicate-entity-UUIDs.patch} | 0 ...ayers-from-nbt-components-configura.patch} | 0 ...component-extra-empty-array-exploit.patch} | 4 +- ...epth-limit-to-Component-deserializer.patch | 163 ++++++++++++++++++ 11 files changed, 166 insertions(+), 105 deletions(-) rename patches/server/{0039-Patch-invalid-entity-rotation-log-spam.patch => 0038-Patch-invalid-entity-rotation-log-spam.patch} (100%) delete mode 100644 patches/server/0038-Reject-translatable-components-with-more-than-32-pla.patch rename patches/server/{0040-Patch-large-selector-distance-crash.patch => 0039-Patch-large-selector-distance-crash.patch} (100%) rename patches/server/{0041-Limit-sculk-catalyst-cursor-positions.patch => 0040-Limit-sculk-catalyst-cursor-positions.patch} (100%) rename patches/server/{0042-Limit-map-decoration-text-length.patch => 0041-Limit-map-decoration-text-length.patch} (100%) rename patches/server/{0043-Limit-map-decoration-count.patch => 0042-Limit-map-decoration-count.patch} (100%) rename patches/server/{0044-Prevent-player-banning-using-duplicate-UUIDs.patch => 0043-Prevent-player-banning-using-duplicate-UUIDs.patch} (92%) rename patches/server/{0045-Don-t-warn-on-duplicate-entity-UUIDs.patch => 0044-Don-t-warn-on-duplicate-entity-UUIDs.patch} (100%) rename patches/server/{0046-Make-excluding-players-from-nbt-components-configura.patch => 0045-Make-excluding-players-from-nbt-components-configura.patch} (100%) rename patches/server/{0047-Fix-component-extra-empty-array-exploit.patch => 0046-Fix-component-extra-empty-array-exploit.patch} (86%) create mode 100644 patches/server/0047-Add-depth-limit-to-Component-deserializer.patch diff --git a/patches/server/0039-Patch-invalid-entity-rotation-log-spam.patch b/patches/server/0038-Patch-invalid-entity-rotation-log-spam.patch similarity index 100% rename from patches/server/0039-Patch-invalid-entity-rotation-log-spam.patch rename to patches/server/0038-Patch-invalid-entity-rotation-log-spam.patch diff --git a/patches/server/0038-Reject-translatable-components-with-more-than-32-pla.patch b/patches/server/0038-Reject-translatable-components-with-more-than-32-pla.patch deleted file mode 100644 index 0cab4af..0000000 --- a/patches/server/0038-Reject-translatable-components-with-more-than-32-pla.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: VideoGameSmash12 -Date: Thu, 16 Mar 2023 01:42:08 -0500 -Subject: [PATCH] Reject translatable components with more than 32 placeholders - - -diff --git a/src/main/java/net/minecraft/network/chat/Component.java b/src/main/java/net/minecraft/network/chat/Component.java -index 3c0ee4e1f42f6056ca86a6f9f129d467e29a2fbc..534f1e6092d2b64b6e6e3182eac0cc38718ab2f8 100644 ---- a/src/main/java/net/minecraft/network/chat/Component.java -+++ b/src/main/java/net/minecraft/network/chat/Component.java -@@ -26,6 +26,7 @@ import java.util.List; - import java.util.Map.Entry; - import java.util.Optional; - import javax.annotation.Nullable; -+ - import net.minecraft.ChatFormatting; - import net.minecraft.Util; - import net.minecraft.network.chat.contents.BlockDataSource; -@@ -44,6 +45,9 @@ import net.minecraft.util.GsonHelper; - import net.minecraft.util.LowerCaseEnumTypeAdapterFactory; - // CraftBukkit start - import com.google.common.collect.Streams; -+ -+import java.util.regex.Matcher; -+import java.util.regex.Pattern; - import java.util.stream.Stream; - // CraftBukkit end - -@@ -255,6 +259,58 @@ public interface Component extends Message, FormattedText, Iterable { - } - }); - -+ private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("%[0-9]{1,}\\$s"); -+ -+ // Scissors start - Calculate number of placeholders in translatable components before serializing them -+ private long calculatePlaceholderCount(JsonElement element) -+ { -+ long amount = 0; -+ -+ if (!element.isJsonObject()) -+ { -+ return amount; -+ } -+ -+ JsonObject from = element.getAsJsonObject(); -+ -+ // Figure out how many placeholders are in a single translatable component -+ if (from.has("translate") && from.get("translate").isJsonPrimitive()) -+ { -+ String key = GsonHelper.getAsString(from, "translate"); -+ Matcher matcher = PLACEHOLDER_PATTERN.matcher(key); -+ amount += matcher.results().count(); -+ -+ // Recursively figure out how many placeholders the component has in the "with" shit -+ if (from.has("with") && from.get("with").isJsonArray()) -+ { -+ JsonArray array = GsonHelper.getAsJsonArray(from, "with"); -+ -+ for (JsonElement within : array) -+ { -+ long amountWithin = calculatePlaceholderCount(within); -+ -+ if (amountWithin == 1) -+ { -+ amount++; -+ } -+ else if (amountWithin > 1) -+ { -+ amount = amount * amountWithin; -+ } -+ } -+ } -+ } -+ // Also applies to keybind components, but to a lesser extent -+ else if (from.has("keybind") && from.get("keybind").isJsonPrimitive()) -+ { -+ String key = GsonHelper.getAsString(from, "keybind"); -+ Matcher matcher = PLACEHOLDER_PATTERN.matcher(key); -+ amount += matcher.results().count(); -+ } -+ return amount; -+ } -+ // Scissors end -+ - public Serializer() {} - - public MutableComponent deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { -@@ -287,6 +343,14 @@ public interface Component extends Message, FormattedText, Iterable { - } - } else { - JsonObject jsonobject = jsonelement.getAsJsonObject(); -+ -+ // Scissors start - Reject translatable components with more than 32 placeholders in them -+ if (calculatePlaceholderCount(jsonobject) > 32) -+ { -+ return Component.empty().append("*** Component has too many placeholders ***").withStyle(ChatFormatting.RED); -+ } -+ // Scissors end -+ - String s; - - if (jsonobject.has("text")) { diff --git a/patches/server/0040-Patch-large-selector-distance-crash.patch b/patches/server/0039-Patch-large-selector-distance-crash.patch similarity index 100% rename from patches/server/0040-Patch-large-selector-distance-crash.patch rename to patches/server/0039-Patch-large-selector-distance-crash.patch diff --git a/patches/server/0041-Limit-sculk-catalyst-cursor-positions.patch b/patches/server/0040-Limit-sculk-catalyst-cursor-positions.patch similarity index 100% rename from patches/server/0041-Limit-sculk-catalyst-cursor-positions.patch rename to patches/server/0040-Limit-sculk-catalyst-cursor-positions.patch diff --git a/patches/server/0042-Limit-map-decoration-text-length.patch b/patches/server/0041-Limit-map-decoration-text-length.patch similarity index 100% rename from patches/server/0042-Limit-map-decoration-text-length.patch rename to patches/server/0041-Limit-map-decoration-text-length.patch diff --git a/patches/server/0043-Limit-map-decoration-count.patch b/patches/server/0042-Limit-map-decoration-count.patch similarity index 100% rename from patches/server/0043-Limit-map-decoration-count.patch rename to patches/server/0042-Limit-map-decoration-count.patch diff --git a/patches/server/0044-Prevent-player-banning-using-duplicate-UUIDs.patch b/patches/server/0043-Prevent-player-banning-using-duplicate-UUIDs.patch similarity index 92% rename from patches/server/0044-Prevent-player-banning-using-duplicate-UUIDs.patch rename to patches/server/0043-Prevent-player-banning-using-duplicate-UUIDs.patch index 145dd2f..ddd93b7 100644 --- a/patches/server/0044-Prevent-player-banning-using-duplicate-UUIDs.patch +++ b/patches/server/0043-Prevent-player-banning-using-duplicate-UUIDs.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Prevent player banning using duplicate UUIDs diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 3bb63a652aca3c23f5f1bbf9cb70fce6540f2e33..934c0d14bd4ac1ece0c0f6add0e3e996e85a9b93 100644 +index 45804711255f04110e9509df8d60900314aa10b7..b1fd209b2893d2d6bfc2ae552c7919ab8abda695 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -1468,7 +1468,14 @@ public class ServerLevel extends Level implements WorldGenLevel { diff --git a/patches/server/0045-Don-t-warn-on-duplicate-entity-UUIDs.patch b/patches/server/0044-Don-t-warn-on-duplicate-entity-UUIDs.patch similarity index 100% rename from patches/server/0045-Don-t-warn-on-duplicate-entity-UUIDs.patch rename to patches/server/0044-Don-t-warn-on-duplicate-entity-UUIDs.patch diff --git a/patches/server/0046-Make-excluding-players-from-nbt-components-configura.patch b/patches/server/0045-Make-excluding-players-from-nbt-components-configura.patch similarity index 100% rename from patches/server/0046-Make-excluding-players-from-nbt-components-configura.patch rename to patches/server/0045-Make-excluding-players-from-nbt-components-configura.patch diff --git a/patches/server/0047-Fix-component-extra-empty-array-exploit.patch b/patches/server/0046-Fix-component-extra-empty-array-exploit.patch similarity index 86% rename from patches/server/0047-Fix-component-extra-empty-array-exploit.patch rename to patches/server/0046-Fix-component-extra-empty-array-exploit.patch index 93847e1..e4acc93 100644 --- a/patches/server/0047-Fix-component-extra-empty-array-exploit.patch +++ b/patches/server/0046-Fix-component-extra-empty-array-exploit.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix component extra empty array exploit diff --git a/src/main/java/net/minecraft/network/chat/Component.java b/src/main/java/net/minecraft/network/chat/Component.java -index 534f1e6092d2b64b6e6e3182eac0cc38718ab2f8..7fcab29427ea030c0d9dcb6ce237b67d98420b55 100644 +index 3c0ee4e1f42f6056ca86a6f9f129d467e29a2fbc..9c2c22ee548ad77f0912698f33de5f467f32fb7f 100644 --- a/src/main/java/net/minecraft/network/chat/Component.java +++ b/src/main/java/net/minecraft/network/chat/Component.java -@@ -322,6 +322,11 @@ public interface Component extends Message, FormattedText, Iterable { +@@ -266,6 +266,11 @@ public interface Component extends Message, FormattedText, Iterable { if (!jsonelement.isJsonObject()) { if (jsonelement.isJsonArray()) { JsonArray jsonarray = jsonelement.getAsJsonArray(); diff --git a/patches/server/0047-Add-depth-limit-to-Component-deserializer.patch b/patches/server/0047-Add-depth-limit-to-Component-deserializer.patch new file mode 100644 index 0000000..c017ffd --- /dev/null +++ b/patches/server/0047-Add-depth-limit-to-Component-deserializer.patch @@ -0,0 +1,163 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Luna +Date: Wed, 31 May 2023 18:14:00 -0300 +Subject: [PATCH] Add depth limit to Component deserializer + + +diff --git a/src/main/java/me/totalfreedom/scissors/ScissorsConfig.java b/src/main/java/me/totalfreedom/scissors/ScissorsConfig.java +index 39b56ca496ed7369ead21805d476c2b813fcdd1d..9659cff6412584190ff0c32e01f602de4ff7d3b3 100644 +--- a/src/main/java/me/totalfreedom/scissors/ScissorsConfig.java ++++ b/src/main/java/me/totalfreedom/scissors/ScissorsConfig.java +@@ -87,8 +87,8 @@ public class ScissorsConfig + config.options().header(HEADER); + config.options().copyDefaults(true); + +- version = getInt("config-version", 4); +- set("config-version", 4); ++ version = getInt("config-version", 5); ++ set("config-version", 5); + readConfig(ScissorsConfig.class, null); + } + +@@ -175,6 +175,12 @@ public class ScissorsConfig + excludePlayersFromNbtComponents = getBoolean("excludePlayersFromNbtComponents", false); + } + ++ public static int componentDepthLimit = 128; ++ private static void componentDepthLimit() ++ { ++ componentDepthLimit = getInt("componentDepthLimit", 128); ++ } ++ + private static void set(String path, Object val) + { + config.set(path, val); +diff --git a/src/main/java/net/minecraft/network/chat/Component.java b/src/main/java/net/minecraft/network/chat/Component.java +index 9c2c22ee548ad77f0912698f33de5f467f32fb7f..ba2879b25e59290ab81501458414a417c121ed03 100644 +--- a/src/main/java/net/minecraft/network/chat/Component.java ++++ b/src/main/java/net/minecraft/network/chat/Component.java +@@ -26,6 +26,7 @@ import java.util.List; + import java.util.Map.Entry; + import java.util.Optional; + import javax.annotation.Nullable; ++import me.totalfreedom.scissors.ScissorsConfig; // Scissors + import net.minecraft.ChatFormatting; + import net.minecraft.Util; + import net.minecraft.network.chat.contents.BlockDataSource; +@@ -44,6 +45,7 @@ import net.minecraft.util.GsonHelper; + import net.minecraft.util.LowerCaseEnumTypeAdapterFactory; + // CraftBukkit start + import com.google.common.collect.Streams; ++import java.util.regex.Pattern; // Scissors + import java.util.stream.Stream; + // CraftBukkit end + +@@ -254,10 +256,16 @@ public interface Component extends Message, FormattedText, Iterable { + throw new IllegalStateException("Couldn't get field 'lineStart' for JsonReader", nosuchfieldexception); + } + }); ++ private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("%[0-9]+\\$s"); // Scissors + + public Serializer() {} + +- public MutableComponent deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ // Scissors start ++ private MutableComponent deserialize(JsonElement jsonelement, JsonDeserializationContext jsondeserializationcontext, int depth) throws JsonParseException { ++ if (depth > ScissorsConfig.componentDepthLimit) { ++ throw new JsonParseException("Depth limit exceeded"); ++ } ++ + if (jsonelement.isJsonPrimitive()) { + return Component.literal(jsonelement.getAsString()); + } else { +@@ -266,18 +274,16 @@ public interface Component extends Message, FormattedText, Iterable { + if (!jsonelement.isJsonObject()) { + if (jsonelement.isJsonArray()) { + JsonArray jsonarray = jsonelement.getAsJsonArray(); +- // Scissors start + if (jsonarray.size() <= 0) { + throw new JsonParseException("Unexpected empty array of components"); + } +- // Scissors end + + ichatmutablecomponent = null; + Iterator iterator = jsonarray.iterator(); + + while (iterator.hasNext()) { + JsonElement jsonelement1 = (JsonElement) iterator.next(); +- MutableComponent ichatmutablecomponent1 = this.deserialize(jsonelement1, jsonelement1.getClass(), jsondeserializationcontext); ++ MutableComponent ichatmutablecomponent1 = this.deserialize(jsonelement1, jsondeserializationcontext, depth + 1); + + if (ichatmutablecomponent == null) { + ichatmutablecomponent = ichatmutablecomponent1; +@@ -301,12 +307,17 @@ public interface Component extends Message, FormattedText, Iterable { + s = GsonHelper.getAsString(jsonobject, "translate"); + String s1 = GsonHelper.getAsString(jsonobject, "fallback", (String) null); + ++ // Penalize depth for placeholders in translate & fallback ++ long translate_placeholders = PLACEHOLDER_PATTERN.matcher(s).results().count(); ++ long fallback_placeholders = s1 != null ? PLACEHOLDER_PATTERN.matcher(s1).results().count() : 0; ++ int penalty = (int)Math.max(translate_placeholders, fallback_placeholders) * 12; ++ + if (jsonobject.has("with")) { + JsonArray jsonarray1 = GsonHelper.getAsJsonArray(jsonobject, "with"); + Object[] aobject = new Object[jsonarray1.size()]; + + for (int i = 0; i < aobject.length; ++i) { +- aobject[i] = Serializer.unwrapTextArgument(this.deserialize(jsonarray1.get(i), type, jsondeserializationcontext)); ++ aobject[i] = Serializer.unwrapTextArgument(this.deserialize(jsonarray1.get(i), jsondeserializationcontext, depth + 1 + penalty)); + } + + ichatmutablecomponent = Component.translatableWithFallback(s, s1, aobject); +@@ -322,7 +333,7 @@ public interface Component extends Message, FormattedText, Iterable { + + ichatmutablecomponent = Component.score(GsonHelper.getAsString(jsonobject1, "name"), GsonHelper.getAsString(jsonobject1, "objective")); + } else if (jsonobject.has("selector")) { +- Optional optional = this.parseSeparator(type, jsondeserializationcontext, jsonobject); ++ Optional optional = this.parseSeparator(jsondeserializationcontext, jsonobject, depth + 1); + + ichatmutablecomponent = Component.selector(GsonHelper.getAsString(jsonobject, "selector"), optional); + } else if (jsonobject.has("keybind")) { +@@ -333,7 +344,7 @@ public interface Component extends Message, FormattedText, Iterable { + } + + s = GsonHelper.getAsString(jsonobject, "nbt"); +- Optional optional1 = this.parseSeparator(type, jsondeserializationcontext, jsonobject); ++ Optional optional1 = this.parseSeparator(jsondeserializationcontext, jsonobject, depth + 1); + boolean flag = GsonHelper.getAsBoolean(jsonobject, "interpret", false); + Object object; + +@@ -360,7 +371,7 @@ public interface Component extends Message, FormattedText, Iterable { + } + + for (int j = 0; j < jsonarray2.size(); ++j) { +- ichatmutablecomponent.append((Component) this.deserialize(jsonarray2.get(j), type, jsondeserializationcontext)); ++ ichatmutablecomponent.append((Component) this.deserialize(jsonarray2.get(j), jsondeserializationcontext, depth + 1)); + } + } + +@@ -370,6 +381,11 @@ public interface Component extends Message, FormattedText, Iterable { + } + } + ++ public MutableComponent deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ return this.deserialize(jsonelement, jsondeserializationcontext, 1); ++ } ++ // Scissors end ++ + private static Object unwrapTextArgument(Object text) { + if (text instanceof Component) { + Component ichatbasecomponent = (Component) text; +@@ -388,8 +404,10 @@ public interface Component extends Message, FormattedText, Iterable { + return text; + } + +- private Optional parseSeparator(Type type, JsonDeserializationContext context, JsonObject json) { +- return json.has("separator") ? Optional.of(this.deserialize(json.get("separator"), type, context)) : Optional.empty(); ++ // Scissors start ++ private Optional parseSeparator(JsonDeserializationContext context, JsonObject json, int depth) { ++ return json.has("separator") ? Optional.of(this.deserialize(json.get("separator"), context, depth + 1)) : Optional.empty(); ++ // Scissors end + } + + private void serializeStyle(Style style, JsonObject json, JsonSerializationContext context) {