diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java index 9408c15..d343a0c 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java @@ -417,23 +417,31 @@ public class ServerBot extends ServerPlayer { super.readAdditionalSaveData(nbt); this.setShiftKeyDown(nbt.getBooleanOr("isShiftKeyDown", false)); - CompoundTag createNbt = nbt.read("createStatus", CompoundTag.CODEC).orElseThrow(); + CompoundTag createNbt = nbt.read("createStatus", CompoundTag.CODEC) + .orElseThrow(() -> new IllegalArgumentException("Missing bot createStatus")); + String rawName = createNbt.getString("rawName") + .orElseGet(() -> createNbt.getString("realName") + .orElseThrow(() -> new IllegalArgumentException("Missing bot rawName"))); + String name = createNbt.getString("name") + .orElseThrow(() -> new IllegalArgumentException("Missing bot name")); + String skinName = createNbt.getStringOr("skinName", rawName); BotCreateState.Builder createBuilder = BotCreateState - .builder(createNbt.getString("rawName") - .orElseGet(() -> createNbt.getString("realName") - .orElseThrow()), null) // Convert from legacy version, consider to use ca.spottedleaf.dataconverter.minecraft.MCDataConverter instead for release version - .name(createNbt.getString("name").orElseThrow()); + .builder(rawName, null) // Convert from legacy version, consider to use ca.spottedleaf.dataconverter.minecraft.MCDataConverter instead for release version + .name(name); String[] skin = null; if (createNbt.contains("skin")) { - ListTag skinTag = createNbt.getList("skin").orElseThrow(); + ListTag skinTag = createNbt.getList("skin") + .orElseThrow(() -> new IllegalArgumentException("Invalid bot skin list")); skin = new String[skinTag.size()]; for (int i = 0; i < skinTag.size(); i++) { - skin[i] = skinTag.getString(i).orElseThrow(); + final int skinIndex = i; + skin[i] = skinTag.getString(i) + .orElseThrow(() -> new IllegalArgumentException("Invalid bot skin entry at index " + skinIndex)); } } - createBuilder.skinName(createNbt.getString("skinName").orElseThrow()).skin(skin); + createBuilder.skinName(skinName).skin(skin); createBuilder.createReason(BotCreateEvent.CreateReason.INTERNAL).creator(null); this.createState = createBuilder.build(); @@ -443,11 +451,17 @@ public class ServerBot extends ServerPlayer { if (FakePlayerCompatConfig.fakePlayerReloadAction && nbt.list("actions", CompoundTag.CODEC).isPresent()) { ValueInput.TypedInputList actionNbt = nbt.list("actions", CompoundTag.CODEC).orElseThrow(); actionNbt.forEach(actionTag -> { - AbstractBotAction action = Actions.getForName(actionTag.getString("actionName").orElseThrow()); - if (action != null) { - AbstractBotAction newAction = action.create(); - newAction.load(actionTag); - this.actions.add(newAction); + try { + String actionName = actionTag.getString("actionName") + .orElseThrow(() -> new IllegalArgumentException("Missing actionName")); + AbstractBotAction action = Actions.getForName(actionName); + if (action != null) { + AbstractBotAction newAction = action.create(); + newAction.load(actionTag); + this.actions.add(newAction); + } + } catch (RuntimeException exception) { + LophineLogger.LOGGER.warn("Skipped invalid saved action for bot {}", this.getScoreboardName(), exception); } }); } @@ -455,10 +469,16 @@ public class ServerBot extends ServerPlayer { if (nbt.list("configs", CompoundTag.CODEC).isPresent()) { ValueInput.TypedInputList configNbt = nbt.list("configs", CompoundTag.CODEC).orElseThrow(); for (CompoundTag configTag : configNbt) { - AbstractBotConfig config = Configs.getConfig(configTag.getString("configName").orElseThrow()); - if (config != null) { - config.setBot(this); - config.load(configTag); + try { + String configName = configTag.getString("configName") + .orElseThrow(() -> new IllegalArgumentException("Missing configName")); + AbstractBotConfig config = Configs.getConfig(configName); + if (config != null) { + config.setBot(this); + config.load(configTag); + } + } catch (RuntimeException exception) { + LophineLogger.LOGGER.warn("Skipped invalid saved config for bot {}", this.getScoreboardName(), exception); } } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java index e8dbb61..bb20d7a 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java @@ -45,6 +45,7 @@ import java.util.jar.JarFile; public class LeavesProtocolManager { private static final Logger LOGGER = LogUtils.getClassLogger(); + private static final LeavesCustomPayload INVALID_PAYLOAD = new InvalidPayload(); private static final Map, PayloadReceiverInvokerHolder> PAYLOAD_RECEIVERS = new HashMap<>(); private static final Map, Identifier> IDS = new HashMap<>(); @@ -217,8 +218,8 @@ public class LeavesProtocolManager { try { return codec.decode(ProtocolUtils.decorate(buf)); } catch (Exception e) { - LOGGER.error("Failed to decode payload {}", location, e); - throw e; + LOGGER.warn("Rejected malformed Leaves payload {}", location, e); + return INVALID_PAYLOAD; } } @@ -238,9 +239,16 @@ public class LeavesProtocolManager { } public static void handlePayload(IdentifierSelector selector, LeavesCustomPayload payload) { + if (payload == INVALID_PAYLOAD) { + return; + } PayloadReceiverInvokerHolder holder; if ((holder = PAYLOAD_RECEIVERS.get(payload.getClass())) != null) { - holder.invoke(selector, payload); + try { + holder.invoke(selector, payload); + } catch (RuntimeException exception) { + LOGGER.warn("Rejected malformed Leaves payload {} from {}", payload.getClass().getName(), describeSelector(selector), exception); + } } } @@ -248,22 +256,44 @@ public class LeavesProtocolManager { RegistryFriendlyByteBuf buf1 = ProtocolUtils.decorate(buf); BytebufReceiverInvokerHolder holder; if ((holder = STRICT_BYTEBUF_RECEIVERS.get(location.toString())) != null) { - holder.invoke(selector, buf1); + safeInvokeBytebuf(holder, selector, location, buf1); return true; } if ((holder = NAMESPACED_BYTEBUF_RECEIVERS.get(location.getNamespace())) != null) { - if (holder.invoke(selector, buf1)) { + if (safeInvokeBytebuf(holder, selector, location, buf1)) { return true; } } for (var holder1 : GENERIC_BYTEBUF_RECEIVERS) { - if (holder1.invoke(selector, buf1)) { + if (safeInvokeBytebuf(holder1, selector, location, buf1)) { return true; } } return false; } + private static boolean safeInvokeBytebuf(BytebufReceiverInvokerHolder holder, IdentifierSelector selector, Identifier location, RegistryFriendlyByteBuf buf) { + try { + return holder.invoke(selector, buf); + } catch (RuntimeException exception) { + LOGGER.warn("Rejected malformed bytebuf payload {} from {}", location, describeSelector(selector), exception); + return true; + } + } + + private static String describeSelector(IdentifierSelector selector) { + if (selector.player() != null) { + return selector.player().getScoreboardName(); + } + if (selector.context() != null) { + return selector.context().profile().name(); + } + return "unknown"; + } + + private record InvalidPayload() implements LeavesCustomPayload { + } + public static void handleTick() { long currentTime = System.currentTimeMillis() / MinecraftServer.getServer().tickRateManager().nanosecondsPerTick(); if (currentTime == lastAcceptTime) return; @@ -414,4 +444,4 @@ public class LeavesProtocolManager { } } } -} \ No newline at end of file +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/rei/REIServerProtocol.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/rei/REIServerProtocol.java index 4e21bd9..71155bf 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/rei/REIServerProtocol.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/rei/REIServerProtocol.java @@ -400,15 +400,25 @@ public class REIServerProtocol implements LeavesProtocol { private static List> readInputs(RegistryAccess registryAccess, ListTag tag) { List> items = new ArrayList<>(); for (Tag t : tag) { - CompoundTag compoundTag = (CompoundTag) t; - compoundTag.getInt("Index").orElseThrow(); + if (!(t instanceof CompoundTag compoundTag)) { + throw new IllegalStateException("Invalid REI input entry"); + } + if (compoundTag.getInt("Index").isEmpty()) { + throw new IllegalStateException("Missing REI input index"); + } ListTag ingredientList = compoundTag.getListOrEmpty("Ingredient"); List slotItems = new ArrayList<>(); for (Tag ingredient : ingredientList) { - CompoundTag ingredientTag = (CompoundTag) ingredient; + if (!(ingredient instanceof CompoundTag ingredientTag)) { + throw new IllegalStateException("Invalid REI ingredient entry"); + } + Tag value = ingredientTag.get("value"); + if (value == null) { + throw new IllegalStateException("Missing REI ingredient value"); + } ItemStack stack = ItemStack.OPTIONAL_CODEC.parse( registryAccess.createSerializationContext(NbtOps.INSTANCE), - ingredientTag.get("value") + value ).getOrThrow(); slotItems.add(stack); } @@ -420,16 +430,28 @@ public class REIServerProtocol implements LeavesProtocol { private static List readSlots(AbstractContainerMenu menu, ServerPlayer player, ListTag tag) { List slots = new ArrayList<>(); for (Tag t : tag) { - CompoundTag compoundTag = (CompoundTag) t; - String id = compoundTag.getString("id").orElseThrow(); + if (!(t instanceof CompoundTag compoundTag)) { + throw new IllegalStateException("Invalid REI slot entry"); + } + String id = compoundTag.getString("id").orElseThrow(() -> new IllegalStateException("Missing REI slot id")); if (!id.startsWith(PROTOCOL_ID + ":")) { throw new IllegalStateException("Invalid slot id: " + id + ", expected to start with '" + PROTOCOL_ID + ":'"); } id = id.substring((PROTOCOL_ID + ":").length()); - int slot = compoundTag.getInt("Slot").orElseThrow(); + int slot = compoundTag.getInt("Slot").orElseThrow(() -> new IllegalStateException("Missing REI slot index")); SlotAccessor accessor = switch (id) { - case "vanilla" -> new VanillaSlotAccessor(menu.slots.get(slot)); - case "player" -> new PlayerInventorySlotAccessor(player, slot); + case "vanilla" -> { + if (slot < 0 || slot >= menu.slots.size()) { + throw new IllegalStateException("Invalid vanilla slot index: " + slot); + } + yield new VanillaSlotAccessor(menu.slots.get(slot)); + } + case "player" -> { + if (slot < 0 || slot >= player.getInventory().getContainerSize()) { + throw new IllegalStateException("Invalid player slot index: " + slot); + } + yield new PlayerInventorySlotAccessor(player, slot); + } default -> throw new IllegalStateException("Unknown container id: " + id); }; slots.add(accessor); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/LitematicaSchematic.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/LitematicaSchematic.java index 1048b28..7ea48b0 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/LitematicaSchematic.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/LitematicaSchematic.java @@ -51,17 +51,21 @@ public record LitematicaSchematic(Map subRegions, SchematicMe public static final int MINECRAFT_DATA_VERSION = SharedConstants.getProtocolVersion(); public static final int SCHEMATIC_VERSION = 7; + private static final long MAX_REGION_VOLUME = 16_777_216L; @NotNull @Contract("_ -> new") public static LitematicaSchematic readFromNBT(@NotNull CompoundTag nbt) { if (nbt.contains("Version")) { final int version = nbt.getIntOr("Version", -1); - final int minecraftDataVersion = nbt.contains("MinecraftDataVersion") ? nbt.getInt("MinecraftDataVersion").orElseThrow() : SharedConstants.getProtocolVersion(); + final int minecraftDataVersion = nbt.getIntOr("MinecraftDataVersion", SharedConstants.getProtocolVersion()); if (version >= 1 && version <= SCHEMATIC_VERSION) { SchematicMetadata metadata = SchematicMetadata.readFromNBT(nbt.getCompoundOrEmpty("Metadata"), version, minecraftDataVersion, FileType.LITEMATICA_SCHEMATIC); Map subRegions = readSubRegionsFromNBT(nbt.getCompoundOrEmpty("Regions"), version, minecraftDataVersion); + if (subRegions.isEmpty()) { + throw new IllegalArgumentException("Schematic has no regions"); + } return new LitematicaSchematic(subRegions, metadata); } else { throw new RuntimeException("Unsupported or future schematic version"); @@ -116,6 +120,7 @@ public record LitematicaSchematic(Map subRegions, SchematicMe if (position == null || size == null) { throw new IllegalArgumentException("Invalid region"); } + validateRegionSize(size); Map tileEntities; List entities; @@ -141,6 +146,9 @@ public record LitematicaSchematic(Map subRegions, SchematicMe Tag blockState = regionTag.get("BlockStates"); if (blockState != null && blockState.getId() == Tag.TAG_LONG_ARRAY) { ListTag palette = regionTag.getListOrEmpty("BlockStatePalette"); + if (palette.isEmpty()) { + throw new IllegalArgumentException("Missing block state palette"); + } long[] blockStateArr = ((LongArrayTag) blockState).getAsLongArray(); BlockPos posEndRel = PositionUtils.getRelativeEndPositionFromAreaSize(size).offset(position); BlockPos posMin = PositionUtils.getMinCorner(position, posEndRel); @@ -155,6 +163,19 @@ public record LitematicaSchematic(Map subRegions, SchematicMe return new SubRegion(blockContainers, tileEntities, pendingBlockTicks, pendingFluidTicks, entities, position, size); } + private static void validateRegionSize(BlockPos size) { + long x = Math.abs((long) size.getX()); + long y = Math.abs((long) size.getY()); + long z = Math.abs((long) size.getZ()); + if (x == 0 || y == 0 || z == 0) { + throw new IllegalArgumentException("Region has zero size"); + } + long volume = Math.multiplyExact(Math.multiplyExact(x, y), z); + if (volume > MAX_REGION_VOLUME) { + throw new IllegalArgumentException("Region volume too large: " + volume); + } + } + private static List readEntitiesFromNBT(ListTag tagList) { List entityList = new ArrayList<>(); final int size = tagList.size(); @@ -278,4 +299,4 @@ public record LitematicaSchematic(Map subRegions, SchematicMe this.nbt = nbt; } } -} \ No newline at end of file +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/ServuxLitematicsProtocol.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/ServuxLitematicsProtocol.java index 9f49ddd..e71af3b 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/ServuxLitematicsProtocol.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/ServuxLitematicsProtocol.java @@ -264,12 +264,17 @@ public class ServuxLitematicsProtocol implements LeavesProtocol { } if (tags.getStringOr("Task", "").equals("LitematicaPaste")) { - ServuxProtocol.LOGGER.debug("litematic_data: Servux Paste request from player {}", player.getName().getString()); - ServerLevel serverLevel = player.level(); - long timeStart = System.currentTimeMillis(); - SchematicPlacement placement = SchematicPlacement.createFromNbt(tags); - ReplaceBehavior replaceMode = ReplaceBehavior.fromStringStatic(tags.getStringOr("ReplaceMode", ReplaceBehavior.NONE.name())); - placement.pasteTo(serverLevel, replaceMode, player, timeStart); + try { + ServuxProtocol.LOGGER.debug("litematic_data: Servux Paste request from player {}", player.getName().getString()); + ServerLevel serverLevel = player.level(); + long timeStart = System.currentTimeMillis(); + SchematicPlacement placement = SchematicPlacement.createFromNbt(tags); + ReplaceBehavior replaceMode = ReplaceBehavior.fromStringStatic(tags.getStringOr("ReplaceMode", ReplaceBehavior.NONE.name())); + placement.pasteTo(serverLevel, replaceMode, player, timeStart); + } catch (RuntimeException exception) { + player.getBukkitEntity().sendActionBar(Component.text("Invalid Litematica paste data", NamedTextColor.RED)); + ServuxProtocol.LOGGER.warn("Rejected invalid Litematica paste request from {}", player.getScoreboardName(), exception); + } } } @@ -438,4 +443,4 @@ public class ServuxLitematicsProtocol implements LeavesProtocol { return !this.hasBuffer() && !this.hasNbt(); } } -} \ No newline at end of file +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBitArray.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBitArray.java index e503792..cc7a035 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBitArray.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBitArray.java @@ -47,11 +47,19 @@ public class LitematicaBitArray { public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn, @Nullable long[] longArrayIn) { Validate.inclusiveBetween(1L, 32L, bitsPerEntryIn); + Validate.isTrue(arraySizeIn >= 0L, "arraySize must not be negative"); this.arraySize = arraySizeIn; this.bitsPerEntry = bitsPerEntryIn; this.maxEntryValue = (1L << bitsPerEntryIn) - 1L; - this.longArray = Objects.requireNonNullElseGet(longArrayIn, () -> new long[(int) (roundUp(arraySizeIn * bitsPerEntryIn, 64L) / 64L)]); + long backingLength = roundUp(Math.multiplyExact(arraySizeIn, bitsPerEntryIn), 64L) / 64L; + Validate.isTrue(backingLength <= Integer.MAX_VALUE, "BitArray backing storage is too large"); + if (longArrayIn != null) { + Validate.isTrue(longArrayIn.length >= backingLength, "BitArray backing storage is too small"); + this.longArray = longArrayIn; + } else { + this.longArray = new long[(int) backingLength]; + } } public static long roundUp(long value, long interval) { @@ -71,6 +79,7 @@ public class LitematicaBitArray { } public void setAt(long index, int value) { + this.checkIndex(index); long startOffset = index * (long) this.bitsPerEntry; int startArrIndex = (int) (startOffset >> 6); // startOffset / 64 int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6); @@ -85,6 +94,7 @@ public class LitematicaBitArray { } public int getAt(long index) { + this.checkIndex(index); long startOffset = index * (long) this.bitsPerEntry; int startArrIndex = (int) (startOffset >> 6); // startOffset / 64 int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6); @@ -101,4 +111,10 @@ public class LitematicaBitArray { public long size() { return this.arraySize; } -} \ No newline at end of file + + private void checkIndex(long index) { + if (index < 0L || index >= this.arraySize) { + throw new IndexOutOfBoundsException("BitArray index " + index + " out of bounds for length " + this.arraySize); + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBlockStatePalette.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBlockStatePalette.java index ed737ea..d8ee022 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBlockStatePalette.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/container/LitematicaBlockStatePalette.java @@ -49,7 +49,9 @@ public interface LitematicaBlockStatePalette { final int size = tagList.size(); for (int i = 0; i < size; ++i) { - CompoundTag tag = tagList.getCompound(i).orElseThrow(); + final int paletteIndex = i; + CompoundTag tag = tagList.getCompound(i) + .orElseThrow(() -> new IllegalArgumentException("Invalid block state palette entry at index " + paletteIndex)); BlockState state = NbtUtils.readBlockState(lookup, tag); if (i > 0 || state != LitematicaBlockStateContainer.AIR_BLOCK_STATE) { @@ -59,4 +61,4 @@ public interface LitematicaBlockStatePalette { } ListTag writeToNBT(); -} \ No newline at end of file +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/placement/SchematicPlacement.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/placement/SchematicPlacement.java index dca2a51..1c5a3cd 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/placement/SchematicPlacement.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/servux/litematics/placement/SchematicPlacement.java @@ -62,20 +62,31 @@ public class SchematicPlacement { } public static SchematicPlacement createFromNbt(CompoundTag tags) { + if (!tags.contains("Schematics")) { + throw new IllegalArgumentException("Missing Schematics tag"); + } + BlockPos origin = NbtUtils.readBlockPosFromArrayTag(tags, "Origin"); + if (origin == null) { + throw new IllegalArgumentException("Missing or invalid Origin tag"); + } SchematicPlacement placement = new SchematicPlacement( LitematicaSchematic.readFromNBT(tags.getCompoundOrEmpty("Schematics")), - NbtUtils.readBlockPosFromArrayTag(tags, "Origin"), + origin, tags.getStringOr("Name", "") ); - placement.mirror = Mirror.values()[tags.getIntOr("Mirror", 0)]; - placement.rotation = Rotation.values()[tags.getIntOr("Rotation", 0)]; + placement.mirror = enumByOrdinal(Mirror.values(), tags.getIntOr("Mirror", 0), "Mirror"); + placement.rotation = enumByOrdinal(Rotation.values(), tags.getIntOr("Rotation", 0), "Rotation"); for (String name : tags.getCompoundOrEmpty("SubRegions").keySet()) { CompoundTag compound = tags.getCompoundOrEmpty("SubRegions").getCompoundOrEmpty(name); + BlockPos pos = NbtUtils.readBlockPosFromArrayTag(compound, "Pos"); + if (pos == null) { + throw new IllegalArgumentException("Missing or invalid Pos tag for sub-region " + name); + } var sub = new SubRegionPlacement( compound.getStringOr("Name", "?"), - NbtUtils.readBlockPosFromArrayTag(compound, "Pos"), - Rotation.values()[compound.getIntOr("Rotation", 0)], - Mirror.values()[compound.getIntOr("Mirror", 0)], + pos, + enumByOrdinal(Rotation.values(), compound.getIntOr("Rotation", 0), "SubRegions." + name + ".Rotation"), + enumByOrdinal(Mirror.values(), compound.getIntOr("Mirror", 0), "SubRegions." + name + ".Mirror"), compound.getBooleanOr("Enabled", true), compound.getBooleanOr("IgnoreEntities", false) ); @@ -84,6 +95,13 @@ public class SchematicPlacement { return placement; } + private static T enumByOrdinal(T[] values, int ordinal, String tagName) { + if (ordinal < 0 || ordinal >= values.length) { + throw new IllegalArgumentException("Invalid " + tagName + " ordinal: " + ordinal); + } + return values[ordinal]; + } + public static IntBoundingBox getBoundsWithinChunkForBox(Box box, int chunkX, int chunkZ) { final int chunkXMin = chunkX << 4; final int chunkZMin = chunkZ << 4; @@ -302,7 +320,13 @@ public class SchematicPlacement { AtomicInteger count_full = new AtomicInteger(); AtomicInteger count = new AtomicInteger(); - streamChunkPos(Objects.requireNonNull(enclosingBox.toVanilla())).forEach(chunkPos -> { + BlockBox vanillaBox = enclosingBox.toVanilla(); + if (vanillaBox == null) { + ServuxProtocol.LOGGER.error("receiver a null vanilla enclosing box"); + return; + } + + streamChunkPos(vanillaBox).forEach(chunkPos -> { RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( serverWorld, chunkPos.x(), diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java index d84b4ff..5ef2bd5 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java @@ -51,6 +51,7 @@ public class CommunicationManager implements LeavesProtocol { protected static final Map modifyState = new ConcurrentHashMap<>(); protected static final Rotation[] rotOrdinals = Rotation.values(); protected static final Mirror[] mirOrdinals = Mirror.values(); + private static final int MAX_SUB_REGION_MODIFICATIONS = 4096; private static final Map> downloadingFile = new ConcurrentHashMap<>(); private static final Map playerMap = new ConcurrentHashMap<>(); @@ -87,7 +88,11 @@ public class CommunicationManager implements LeavesProtocol { @ProtocolHandler.PayloadReceiver(payload = SyncmaticaPayload.class) public static void onPacketGet(ServerPlayer player, SyncmaticaPayload payload) { - onPacket(player.connection.exchangeTarget, payload.packetType(), payload.data()); + try { + onPacket(player.connection.exchangeTarget, payload.packetType(), payload.data()); + } catch (IllegalArgumentException | IndexOutOfBoundsException exception) { + LOGGER.warn("Rejected malformed Syncmatica packet {} from {}", payload.packetType(), player.getScoreboardName(), exception); + } } public static void onPacket(final @NotNull ExchangeTarget source, final Identifier id, final FriendlyByteBuf packetBuf) { @@ -326,20 +331,30 @@ public class CommunicationManager implements LeavesProtocol { public static void receivePositionData(final @NotNull ServerPlacement placement, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { final BlockPos pos = buf.readBlockPos(); final String dimensionId = buf.readUtf(32767); - final Rotation rot = rotOrdinals[buf.readInt()]; - final Mirror mir = mirOrdinals[buf.readInt()]; + final Rotation rot = readOrdinal(rotOrdinals, buf.readInt(), "rotation"); + final Mirror mir = readOrdinal(mirOrdinals, buf.readInt(), "mirror"); placement.move(dimensionId, pos, rot, mir); if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { final SubRegionData subRegionData = placement.getSubRegionData(); subRegionData.reset(); final int limit = buf.readInt(); + if (limit < 0 || limit > MAX_SUB_REGION_MODIFICATIONS) { + throw new IllegalArgumentException("Invalid sub-region modification count: " + limit); + } for (int i = 0; i < limit; i++) { - subRegionData.modify(buf.readUtf(32767), buf.readBlockPos(), rotOrdinals[buf.readInt()], mirOrdinals[buf.readInt()]); + subRegionData.modify(buf.readUtf(32767), buf.readBlockPos(), readOrdinal(rotOrdinals, buf.readInt(), "sub-region rotation"), readOrdinal(mirOrdinals, buf.readInt(), "sub-region mirror")); } } } + private static T readOrdinal(T[] values, int ordinal, String name) { + if (ordinal < 0 || ordinal >= values.length) { + throw new IllegalArgumentException("Invalid " + name + " ordinal: " + ordinal); + } + return values[ordinal]; + } + public static void download(final ServerPlacement syncmatic, final ExchangeTarget source) throws NoSuchAlgorithmException, IOException { if (!SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).isReadyForDownload()) { throw new IllegalArgumentException(syncmatic.toString() + " is not ready for download local state is: " + SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).toString());