From 058846647c7ab46ae49817cd7dc99b97d22861d3 Mon Sep 17 00:00:00 2001 From: Luna Date: Wed, 31 May 2023 20:45:19 -0300 Subject: [PATCH] Add depth limit to Component deserializer --- ...Patch-large-selector-distance-crash.patch} | 0 ...ble-components-with-more-than-32-pla.patch | 89 ----------- ...48-Limit-map-decoration-text-length.patch} | 0 ... => 0049-Limit-map-decoration-count.patch} | 0 ...layer-banning-using-duplicate-UUIDs.patch} | 0 ...on-t-warn-on-duplicate-entity-UUIDs.patch} | 0 ...component-extra-empty-array-exploit.patch} | 4 +- ...epth-limit-to-Component-deserializer.patch | 151 ++++++++++++++++++ 8 files changed, 153 insertions(+), 91 deletions(-) rename patches/server/{0048-Patch-large-selector-distance-crash.patch => 0047-Patch-large-selector-distance-crash.patch} (100%) delete mode 100644 patches/server/0047-Reject-translatable-components-with-more-than-32-pla.patch rename patches/server/{0049-Limit-map-decoration-text-length.patch => 0048-Limit-map-decoration-text-length.patch} (100%) rename patches/server/{0050-Limit-map-decoration-count.patch => 0049-Limit-map-decoration-count.patch} (100%) rename patches/server/{0051-Prevent-player-banning-using-duplicate-UUIDs.patch => 0050-Prevent-player-banning-using-duplicate-UUIDs.patch} (100%) rename patches/server/{0052-Don-t-warn-on-duplicate-entity-UUIDs.patch => 0051-Don-t-warn-on-duplicate-entity-UUIDs.patch} (100%) rename patches/server/{0053-Fix-component-extra-empty-array-exploit.patch => 0052-Fix-component-extra-empty-array-exploit.patch} (87%) create mode 100644 patches/server/0053-Add-depth-limit-to-Component-deserializer.patch diff --git a/patches/server/0048-Patch-large-selector-distance-crash.patch b/patches/server/0047-Patch-large-selector-distance-crash.patch similarity index 100% rename from patches/server/0048-Patch-large-selector-distance-crash.patch rename to patches/server/0047-Patch-large-selector-distance-crash.patch diff --git a/patches/server/0047-Reject-translatable-components-with-more-than-32-pla.patch b/patches/server/0047-Reject-translatable-components-with-more-than-32-pla.patch deleted file mode 100644 index 82e5d54..0000000 --- a/patches/server/0047-Reject-translatable-components-with-more-than-32-pla.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: VideoGameSmash12 -Date: Wed, 19 Oct 2022 00:50:29 -0600 -Subject: [PATCH] Reject translatable components with more than 32 placeholders - in them - - -diff --git a/src/main/java/net/minecraft/network/chat/Component.java b/src/main/java/net/minecraft/network/chat/Component.java -index 6978d14c6bd90ffb640e39e8666430d95d5ef45c..e6d733fc3a4acdb3fd91b5644285854943a02761 100644 ---- a/src/main/java/net/minecraft/network/chat/Component.java -+++ b/src/main/java/net/minecraft/network/chat/Component.java -@@ -33,6 +33,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 - -@@ -192,9 +195,53 @@ 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]{1,}\\$s"); - - public Serializer() {} - -+ // 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 MutableComponent deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { - if (jsonelement.isJsonPrimitive()) { - return new TextComponent(jsonelement.getAsString()); -@@ -221,6 +268,13 @@ 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 new TextComponent("*** Component has too many placeholders ***").withStyle(ChatFormatting.RED); -+ } -+ // Scissors end -+ - Object object; - - if (jsonobject.has("text")) { diff --git a/patches/server/0049-Limit-map-decoration-text-length.patch b/patches/server/0048-Limit-map-decoration-text-length.patch similarity index 100% rename from patches/server/0049-Limit-map-decoration-text-length.patch rename to patches/server/0048-Limit-map-decoration-text-length.patch diff --git a/patches/server/0050-Limit-map-decoration-count.patch b/patches/server/0049-Limit-map-decoration-count.patch similarity index 100% rename from patches/server/0050-Limit-map-decoration-count.patch rename to patches/server/0049-Limit-map-decoration-count.patch diff --git a/patches/server/0051-Prevent-player-banning-using-duplicate-UUIDs.patch b/patches/server/0050-Prevent-player-banning-using-duplicate-UUIDs.patch similarity index 100% rename from patches/server/0051-Prevent-player-banning-using-duplicate-UUIDs.patch rename to patches/server/0050-Prevent-player-banning-using-duplicate-UUIDs.patch diff --git a/patches/server/0052-Don-t-warn-on-duplicate-entity-UUIDs.patch b/patches/server/0051-Don-t-warn-on-duplicate-entity-UUIDs.patch similarity index 100% rename from patches/server/0052-Don-t-warn-on-duplicate-entity-UUIDs.patch rename to patches/server/0051-Don-t-warn-on-duplicate-entity-UUIDs.patch diff --git a/patches/server/0053-Fix-component-extra-empty-array-exploit.patch b/patches/server/0052-Fix-component-extra-empty-array-exploit.patch similarity index 87% rename from patches/server/0053-Fix-component-extra-empty-array-exploit.patch rename to patches/server/0052-Fix-component-extra-empty-array-exploit.patch index a9172ec..5d10dc0 100644 --- a/patches/server/0053-Fix-component-extra-empty-array-exploit.patch +++ b/patches/server/0052-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 e6d733fc3a4acdb3fd91b5644285854943a02761..68d7739e44dc31ddf3f3c4fca4b3b7095060ac52 100644 +index 6978d14c6bd90ffb640e39e8666430d95d5ef45c..474ed00c09d58a7796cd543c9bcf8752e2f9f742 100644 --- a/src/main/java/net/minecraft/network/chat/Component.java +++ b/src/main/java/net/minecraft/network/chat/Component.java -@@ -248,6 +248,12 @@ public interface Component extends Message, FormattedText, Iterable { +@@ -201,6 +201,12 @@ public interface Component extends Message, FormattedText, Iterable { } else if (!jsonelement.isJsonObject()) { if (jsonelement.isJsonArray()) { JsonArray jsonarray = jsonelement.getAsJsonArray(); diff --git a/patches/server/0053-Add-depth-limit-to-Component-deserializer.patch b/patches/server/0053-Add-depth-limit-to-Component-deserializer.patch new file mode 100644 index 0000000..2dc6c35 --- /dev/null +++ b/patches/server/0053-Add-depth-limit-to-Component-deserializer.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Luna +Date: Wed, 31 May 2023 20:44:20 -0300 +Subject: [PATCH] Add depth limit to Component deserializer + + +diff --git a/src/main/java/com/github/atlasmediagroup/scissors/ScissorsConfig.java b/src/main/java/com/github/atlasmediagroup/scissors/ScissorsConfig.java +index bb58d90e9d676761a43a12cbc1c3b687d00654b1..74f33cba7d713eddca5a58a2b33cad357792145e 100644 +--- a/src/main/java/com/github/atlasmediagroup/scissors/ScissorsConfig.java ++++ b/src/main/java/com/github/atlasmediagroup/scissors/ScissorsConfig.java +@@ -65,8 +65,8 @@ public class ScissorsConfig + config.options().header(HEADER); + config.options().copyDefaults(true); + +- version = getInt("config-version", 3); +- set("config-version", 3); ++ version = getInt("config-version", 4); ++ set("config-version", 4); + readConfig(ScissorsConfig.class, null); + } + +@@ -146,6 +146,12 @@ public class ScissorsConfig + disableGameMasterBlocks = getBoolean("disableGameMasterBlocks", 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 474ed00c09d58a7796cd543c9bcf8752e2f9f742..68e99223b0b084bdfdb9990780150bbc7d8ad8ea 100644 +--- a/src/main/java/net/minecraft/network/chat/Component.java ++++ b/src/main/java/net/minecraft/network/chat/Component.java +@@ -1,5 +1,6 @@ + package net.minecraft.network.chat; + ++import com.github.atlasmediagroup.scissors.ScissorsConfig; // Scissors + import com.google.common.collect.Lists; + import io.papermc.paper.adventure.AdventureComponent; // Paper + import com.google.gson.Gson; +@@ -33,6 +34,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 + +@@ -192,27 +194,31 @@ 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 new TextComponent(jsonelement.getAsString()); + } else 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 + + MutableComponent 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; +@@ -236,12 +242,17 @@ public interface Component extends Message, FormattedText, Iterable { + + if (jsonobject.has("translate")) { + s = GsonHelper.getAsString(jsonobject, "translate"); ++ ++ // Penalize depth for placeholders in translate & fallback ++ long translate_placeholders = PLACEHOLDER_PATTERN.matcher(s).results().count(); ++ int penalty = (int)translate_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] = this.deserialize(jsonarray1.get(i), type, jsondeserializationcontext); ++ aobject[i] = this.deserialize(jsonarray1.get(i), jsondeserializationcontext, depth + 1 + penalty); + if (aobject[i] instanceof TextComponent) { + TextComponent chatcomponenttext = (TextComponent) aobject[i]; + +@@ -264,7 +275,7 @@ public interface Component extends Message, FormattedText, Iterable { + + object = new ScoreComponent(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); + + object = new SelectorComponent(GsonHelper.getAsString(jsonobject, "selector"), optional); + } else if (jsonobject.has("keybind")) { +@@ -275,7 +286,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); + + if (jsonobject.has("block")) { +@@ -300,7 +311,7 @@ public interface Component extends Message, FormattedText, Iterable { + } + + for (int j = 0; j < jsonarray2.size(); ++j) { +- ((MutableComponent) object).append(this.deserialize(jsonarray2.get(j), type, jsondeserializationcontext)); ++ ((MutableComponent) object).append(this.deserialize(jsonarray2.get(j), jsondeserializationcontext, depth + 1)); + } + } + +@@ -309,8 +320,13 @@ public interface Component extends Message, FormattedText, Iterable { + } + } + +- private Optional parseSeparator(Type type, JsonDeserializationContext context, JsonObject json) { +- return json.has("separator") ? Optional.of(this.deserialize(json.get("separator"), type, context)) : Optional.empty(); ++ public MutableComponent deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ return this.deserialize(jsonelement, jsondeserializationcontext, 1); ++ } ++ ++ 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) {