diff --git a/src/main/java/autismclient/mixin/security/AutismProtectorPacketCrashGuardMixin.java b/src/main/java/autismclient/mixin/security/AutismProtectorPacketCrashGuardMixin.java new file mode 100644 index 0000000..bf7d68e --- /dev/null +++ b/src/main/java/autismclient/mixin/security/AutismProtectorPacketCrashGuardMixin.java @@ -0,0 +1,140 @@ +package autismclient.mixin.security; + +import autismclient.security.AutismProtector; +import autismclient.security.AutismProtectorNumericSanity; + +import com.mojang.logging.LogUtils; + +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket; +import net.minecraft.network.protocol.game.ClientboundExplodePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.phys.Vec3; + +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Optional; + +/** + * Packet crash guard — a defensive protector feature that drops an incoming play packet whose raw position or + * velocity is malformed (NaN / Infinity) or absurdly large (beyond {@link AutismProtectorNumericSanity#SANE_LIMIT}, + * far past the world border) before the game applies it and overflows its own section/collision math and crashes. + * + *
Some servers send such values deliberately to crash connected clients; observed crash reports carried + * per-axis values around {@code 1.8e38 / 2.8e38 / 2.1e38}. The crash reproduces even with rendering optimisation + * mods disabled, so it is a vanilla-math overflow rather than an optimisation-mod bug. Covered here: + * + *
Purely defensive and local: it only drops a malformed packet (the client keeps its current, valid + * state) — it never edits anything the client sends and never fabricates a teleport acknowledgement. It only + * fires on values orders of magnitude past anything legitimate, so real gameplay is never touched. Gated by + * {@link AutismProtector#shouldGuardCrashPackets()} (and, like every {@code security} mixin, stood down when an + * external protector such as opsec is present — see {@code AutismMixinPlugin}). + * + *
Injections use {@code require = 0} so that if a future mapping renames one of these handlers the guard + * degrades to "that one packet is not guarded" rather than failing the whole required mixin config — a + * deliberate choice for a defensive safeguard that must never be the reason a client won't launch. + * + *
Adapted for AUTISM from BossCrashGuard (https://github.com/WaterBoss11/BossCrashGuard).
+ */
+@Mixin(ClientPacketListener.class)
+public abstract class AutismProtectorPacketCrashGuardMixin {
+
+ @Unique
+ private static final Logger AUTISM$LOGGER = LogUtils.getLogger();
+
+ @Unique
+ private static long autism$dropCount = 0L;
+
+ @Inject(method = "handleExplosion", at = @At("HEAD"), cancellable = true, require = 0)
+ private void autism$guardExplosion(ClientboundExplodePacket packet, CallbackInfo ci) {
+ if (!AutismProtector.shouldGuardCrashPackets()) return;
+
+ boolean bad = autism$vecInsane(packet.center())
+ || AutismProtectorNumericSanity.isInsane(packet.radius(), AutismProtectorNumericSanity.SANE_LIMIT);
+ if (!bad) {
+ Optional Rather than a one-off fix per exploit packet, any incoming packet handler can call these predicates on the
+ * fields it is about to apply and drop a packet carrying a bad value. Observed overflow-crash reports carried
+ * per-axis values around {@code 1.8e38 / 2.8e38 / 2.1e38} (near {@code Float.MAX_VALUE}); the ceiling here sits
+ * many orders of magnitude below that and far above any legitimate value:
+ *
+ * Purely defensive: these never alter anything the client sends to the server and never fabricate
+ * acknowledgements — they only let a handler avoid processing a value that would crash the local client. No
+ * Minecraft types are referenced, so the whole decision surface is unit-testable without a client.
+ *
+ * Adapted for AUTISM from BossCrashGuard (https://github.com/WaterBoss11/BossCrashGuard).
+ */
+public final class AutismProtectorNumericSanity {
+
+ private AutismProtectorNumericSanity() {
+ }
+
+ /** Ceiling for a position/velocity component the client applies (blocks or blocks/tick). */
+ public static final double SANE_LIMIT = 1.0e9;
+
+ /** True when {@code v} is NaN or infinite — i.e. not a usable finite number. */
+ public static boolean isNonFinite(double v) {
+ return Double.isNaN(v) || Double.isInfinite(v);
+ }
+
+ /** Float overload of {@link #isNonFinite(double)}. */
+ public static boolean isNonFinite(float v) {
+ return Float.isNaN(v) || Float.isInfinite(v);
+ }
+
+ /** True when {@code v} is non-finite, or its magnitude exceeds {@code limit} — unsafe to feed to game math. */
+ public static boolean isInsane(double v, double limit) {
+ return isNonFinite(v) || Math.abs(v) > limit;
+ }
+
+ /** Float overload of {@link #isInsane(double, double)}. */
+ public static boolean isInsane(float v, double limit) {
+ return isNonFinite(v) || Math.abs((double) v) > limit;
+ }
+
+ /**
+ * True when any of {@code x/y/z} is insane against {@link #SANE_LIMIT} — the check every position/velocity
+ * packet path uses before letting the client apply the triple.
+ */
+ public static boolean isInsane(double x, double y, double z) {
+ return isInsane(x, SANE_LIMIT) || isInsane(y, SANE_LIMIT) || isInsane(z, SANE_LIMIT);
+ }
+}
diff --git a/src/main/java/autismclient/util/AutismConfig.java b/src/main/java/autismclient/util/AutismConfig.java
index 3987efa..53ee229 100644
--- a/src/main/java/autismclient/util/AutismConfig.java
+++ b/src/main/java/autismclient/util/AutismConfig.java
@@ -63,6 +63,8 @@ public final class AutismConfig {
public boolean protectorStripServerPacks = false;
public boolean protectorChatSigningOff = false;
+
+ public boolean protectorCrashGuard = true;
public boolean performanceDebug = false;
public boolean inventoryMove = false;
public boolean xCarry = true;
diff --git a/src/main/resources/autism.mixins.json b/src/main/resources/autism.mixins.json
index 13f6937..03089d5 100644
--- a/src/main/resources/autism.mixins.json
+++ b/src/main/resources/autism.mixins.json
@@ -164,6 +164,7 @@
"security.AutismProtectorResourceLoaderImplMixin",
"security.AutismProtectorPacketProcessorMixin",
"security.AutismProtectorPacketDecoderMixin",
+ "security.AutismProtectorPacketCrashGuardMixin",
"security.AutismRegistryDataCollectorMixin",
"security.AutismItemPropertiesMixin",
"security.AutismDataComponentInitializerMixin",
diff --git a/src/test/java/autismclient/security/AutismProtectorNumericSanityTest.java b/src/test/java/autismclient/security/AutismProtectorNumericSanityTest.java
new file mode 100644
index 0000000..9349d58
--- /dev/null
+++ b/src/test/java/autismclient/security/AutismProtectorNumericSanityTest.java
@@ -0,0 +1,42 @@
+package autismclient.security;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class AutismProtectorNumericSanityTest {
+
+ @Test
+ void finiteInRangeValuesAreSane() {
+ assertFalse(AutismProtectorNumericSanity.isNonFinite(0.0));
+ assertFalse(AutismProtectorNumericSanity.isNonFinite(1.0f));
+ assertFalse(AutismProtectorNumericSanity.isInsane(0.0, 0.0, 0.0));
+ // A far-but-legitimate coordinate near the world border stays sane.
+ assertFalse(AutismProtectorNumericSanity.isInsane(2.9e7, 320.0, -2.9e7));
+ // Right at the ceiling is still allowed (strictly-greater is the reject test).
+ assertFalse(AutismProtectorNumericSanity.isInsane(AutismProtectorNumericSanity.SANE_LIMIT,
+ AutismProtectorNumericSanity.SANE_LIMIT));
+ }
+
+ @Test
+ void nonFiniteValuesAreInsane() {
+ assertTrue(AutismProtectorNumericSanity.isNonFinite(Double.NaN));
+ assertTrue(AutismProtectorNumericSanity.isNonFinite(Double.POSITIVE_INFINITY));
+ assertTrue(AutismProtectorNumericSanity.isNonFinite(Float.NaN));
+ assertTrue(AutismProtectorNumericSanity.isNonFinite(Float.NEGATIVE_INFINITY));
+ assertTrue(AutismProtectorNumericSanity.isInsane(Double.NaN, AutismProtectorNumericSanity.SANE_LIMIT));
+ assertTrue(AutismProtectorNumericSanity.isInsane(Float.POSITIVE_INFINITY,
+ AutismProtectorNumericSanity.SANE_LIMIT));
+ }
+
+ @Test
+ void overflowMagnitudesAreInsane() {
+ // The values from real crash reports.
+ assertTrue(AutismProtectorNumericSanity.isInsane(1.8e38, 2.8e38, 2.1e38));
+ assertTrue(AutismProtectorNumericSanity.isInsane(-2.8e38, AutismProtectorNumericSanity.SANE_LIMIT));
+ // Just past the ceiling on any single axis trips the triple check.
+ double justOver = Math.nextUp(AutismProtectorNumericSanity.SANE_LIMIT);
+ assertTrue(AutismProtectorNumericSanity.isInsane(0.0, justOver, 0.0));
+ }
+}
+ *
+ *
+ *