diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java index ffbf6996..170f56be 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java @@ -2,6 +2,7 @@ package com.eu.habbo.habbohotel.items.interactions; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredInputGuard; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; @@ -230,39 +231,18 @@ public abstract class InteractionWired extends InteractionDefault { public static WiredSettings readSettings(ClientMessage packet, boolean isEffect) { - int intParamCount = packet.readInt(); - if (intParamCount < 0 || intParamCount > 100) { - throw new IllegalArgumentException("Invalid intParamCount: " + intParamCount); - } - int[] intParams = new int[intParamCount]; - - for(int i = 0; i < intParamCount; i++) - { - intParams[i] = packet.readInt(); - } - - String stringParam = packet.readString(); - - int itemCount = packet.readInt(); - int selectionLimit = Emulator.getConfig() != null ? Emulator.getConfig().getInt("hotel.wired.furni.selection.count", 5) : 5; - if (itemCount < 0 || itemCount > selectionLimit * 20) { - throw new IllegalArgumentException("Invalid itemCount: " + itemCount + " exceeds maximum allowed limit"); - } - int[] itemIds = new int[itemCount]; - - for(int i = 0; i < itemCount; i++) - { - itemIds[i] = packet.readInt(); - } + int[] intParams = WiredInputGuard.readIntParams(packet); + String stringParam = WiredInputGuard.readStringParam(packet); + int[] itemIds = WiredInputGuard.readFurniIds(packet); WiredSettings settings = new WiredSettings(intParams, stringParam, itemIds, -1); if(isEffect) { - settings.setDelay(packet.readInt()); + settings.setDelay(WiredInputGuard.normalizeDelay(packet.readInt())); } - settings.setStuffTypeSelectionCode(packet.readInt()); + settings.setStuffTypeSelectionCode(WiredInputGuard.normalizeStuffSelectionCode(packet.readInt())); return settings; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredInputGuard.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredInputGuard.java new file mode 100644 index 00000000..709133c6 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredInputGuard.java @@ -0,0 +1,90 @@ +package com.eu.habbo.habbohotel.items.interactions.wired; + +import com.eu.habbo.Emulator; +import com.eu.habbo.messages.ClientMessage; + +import java.util.Arrays; + +public final class WiredInputGuard { + public static final int MAX_INT_PARAMS = 100; + public static final int MAX_STRING_PARAM_LENGTH = 1024; + public static final int MAX_ABSOLUTE_FURNI_IDS = 100; + public static final int DEFAULT_MAX_DELAY = 20; + public static final int MAX_ABSOLUTE_DELAY = 3600; + public static final int MIN_STUFF_SELECTION_CODE = -1; + public static final int MAX_STUFF_SELECTION_CODE = 2; + + private WiredInputGuard() { + } + + public static int[] readIntParams(ClientMessage packet) { + int count = packet.readInt(); + if (count < 0 || count > MAX_INT_PARAMS) { + throw new IllegalArgumentException("Invalid wired int param count"); + } + + int[] values = new int[count]; + for (int i = 0; i < count; i++) { + values[i] = packet.readInt(); + } + return values; + } + + public static String readStringParam(ClientMessage packet) { + String value = packet.readString(); + if (value == null || value.isEmpty()) { + return ""; + } + + return value.length() > MAX_STRING_PARAM_LENGTH + ? value.substring(0, MAX_STRING_PARAM_LENGTH) + : value; + } + + public static int[] readFurniIds(ClientMessage packet) { + int count = packet.readInt(); + int maxCount = maxFurniSelectionCount(); + if (count < 0 || count > maxCount) { + throw new IllegalArgumentException("Invalid wired furni selection count"); + } + + int[] values = new int[count]; + int accepted = 0; + for (int i = 0; i < count; i++) { + int itemId = packet.readInt(); + if (itemId > 0) { + values[accepted++] = itemId; + } + } + + return accepted == values.length ? values : Arrays.copyOf(values, accepted); + } + + public static int normalizeDelay(int delay) { + return Math.max(0, Math.min(delay, maxDelay())); + } + + public static int normalizeStuffSelectionCode(int code) { + if (code < MIN_STUFF_SELECTION_CODE || code > MAX_STUFF_SELECTION_CODE) { + return MIN_STUFF_SELECTION_CODE; + } + + return code; + } + + public static int maxFurniSelectionCount() { + int selectionLimit = Emulator.getConfig() != null + ? Emulator.getConfig().getInt("hotel.wired.furni.selection.count", 5) + : 5; + selectionLimit = Math.max(1, selectionLimit); + return Math.min(MAX_ABSOLUTE_FURNI_IDS, selectionLimit * 20); + } + + public static int maxDelay() { + int configured = Emulator.getConfig() != null + ? Emulator.getConfig().getInt("hotel.wired.max_delay", DEFAULT_MAX_DELAY) + : DEFAULT_MAX_DELAY; + configured = Math.max(0, configured); + return Math.min(MAX_ABSOLUTE_DELAY, configured); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java index c1ca5c37..8efad3e8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java @@ -31,7 +31,13 @@ public class WiredConditionSaveDataEvent extends MessageHandler { if(saveMethod.isPresent()) { if (saveMethod.get().getParameterTypes()[0] == WiredSettings.class) { - WiredSettings settings = InteractionWired.readSettings(this.packet, false); + WiredSettings settings; + try { + settings = InteractionWired.readSettings(this.packet, false); + } catch (IllegalArgumentException e) { + this.client.sendResponse(new UpdateFailedComposer("Invalid wired condition settings")); + return; + } if (condition.saveData(settings)) { this.client.sendResponse(new WiredSavedComposer()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java index 85728ba7..0ae07dc6 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java @@ -28,7 +28,13 @@ public class WiredEffectSaveDataEvent extends MessageHandler { if (effect == null && extra == null) throw new WiredSaveException(String.format("Wired effect/extra with item id %s not found in room", itemId)); - WiredSettings settings = InteractionWired.readSettings(this.packet, true); + WiredSettings settings; + try { + settings = InteractionWired.readSettings(this.packet, true); + } catch (IllegalArgumentException e) { + this.client.sendResponse(new UpdateFailedComposer("Invalid wired effect settings")); + return; + } boolean saved; if (effect != null) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java index 66970dd2..0f503860 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java @@ -22,7 +22,13 @@ public class WiredTriggerSaveDataEvent extends MessageHandler { InteractionWiredTrigger trigger = room.getRoomSpecialTypes().getTrigger(itemId); if (trigger != null) { - WiredSettings settings = InteractionWired.readSettings(this.packet, false); + WiredSettings settings; + try { + settings = InteractionWired.readSettings(this.packet, false); + } catch (IllegalArgumentException e) { + this.client.sendResponse(new UpdateFailedComposer("Invalid wired trigger settings")); + return; + } try { boolean saved = trigger.saveData(settings, this.client); diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredInputGuardTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredInputGuardTest.java new file mode 100644 index 00000000..8341d9bc --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredInputGuardTest.java @@ -0,0 +1,51 @@ +package com.eu.habbo.habbohotel.items.interactions.wired; + +import com.eu.habbo.messages.ClientMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class WiredInputGuardTest { + + @Test + void trimsOversizedStringParams() { + String input = "x".repeat(WiredInputGuard.MAX_STRING_PARAM_LENGTH + 1); + ClientMessage message = new ClientMessage(1, stringBuffer(input)); + + assertEquals(WiredInputGuard.MAX_STRING_PARAM_LENGTH, + WiredInputGuard.readStringParam(message).length()); + } + + @Test + void filtersNonPositiveFurniIds() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeInt(4); + buffer.writeInt(1); + buffer.writeInt(0); + buffer.writeInt(-1); + buffer.writeInt(2); + + assertArrayEquals(new int[]{1, 2}, WiredInputGuard.readFurniIds(new ClientMessage(1, buffer))); + } + + @Test + void clampsDelayAndSelectionCode() { + assertEquals(0, WiredInputGuard.normalizeDelay(-10)); + assertEquals(WiredInputGuard.DEFAULT_MAX_DELAY, WiredInputGuard.normalizeDelay(WiredInputGuard.DEFAULT_MAX_DELAY + 1)); + assertEquals(-1, WiredInputGuard.normalizeStuffSelectionCode(99)); + assertEquals(2, WiredInputGuard.normalizeStuffSelectionCode(2)); + } + + private static ByteBuf stringBuffer(String value) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + ByteBuf buffer = Unpooled.buffer(); + buffer.writeShort(bytes.length); + buffer.writeBytes(bytes); + return buffer; + } +}