fix(P0): 3 exploit fixes from swarm review

- RISK-006: Mittens bypass lock/unlock — add HANDS check in PacketV2SelfLock/Unlock
- RISK-002: Struggle re-roll exploit — reject-if-active in startContinuous*Session()
- RISK-003: Non-V2 locked items bypass conflict resolution — check ILockable before swap
This commit is contained in:
NotEvil
2026-04-16 10:49:04 +02:00
parent bce0598059
commit d75b74f9f9
4 changed files with 24 additions and 10 deletions

View File

@@ -83,16 +83,16 @@ public class StruggleSessionManager {
) { ) {
UUID playerId = player.getUUID(); UUID playerId = player.getUUID();
// Remove any existing continuous session // RISK-002 fix: reject if active session exists (prevents direction re-roll exploit)
ContinuousStruggleMiniGameState existing = continuousSessions.get( ContinuousStruggleMiniGameState existing = continuousSessions.get(
playerId playerId
); );
if (existing != null) { if (existing != null) {
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[StruggleSessionManager] Replacing existing continuous struggle session for {}", "[StruggleSessionManager] Rejected continuous session: active session already exists for {}",
player.getName().getString() player.getName().getString()
); );
continuousSessions.remove(playerId); return null;
} }
// Create new session with configurable rate // Create new session with configurable rate
@@ -146,12 +146,16 @@ public class StruggleSessionManager {
) { ) {
UUID playerId = player.getUUID(); UUID playerId = player.getUUID();
// Remove any existing session // RISK-002 fix: reject if active session exists (prevents direction re-roll exploit)
ContinuousStruggleMiniGameState existing = continuousSessions.get( ContinuousStruggleMiniGameState existing = continuousSessions.get(
playerId playerId
); );
if (existing != null) { if (existing != null) {
continuousSessions.remove(playerId); TiedUpMod.LOGGER.debug(
"[StruggleSessionManager] Rejected accessory session: active session already exists for {}",
player.getName().getString()
);
return null;
} }
// Create new session with target slot and configurable rate // Create new session with target slot and configurable rate

View File

@@ -1,6 +1,7 @@
package com.tiedup.remake.v2.bondage; package com.tiedup.remake.v2.bondage;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
@@ -149,7 +150,14 @@ public final class V2EquipmentManager {
return V2EquipResult.swapped(conflictStack); return V2EquipResult.swapped(conflictStack);
} }
} else { } else {
// Non-V2 item in region — log warning and remove // Non-V2 item in region — RISK-003 fix: check if locked before removing
if (conflictStack.getItem() instanceof ILockable lockable && lockable.isLocked(conflictStack)) {
TiedUpMod.LOGGER.warn(
"V2EquipmentManager: blocked swap of locked non-V2 item {} from equipment",
conflictStack
);
return V2EquipResult.BLOCKED;
}
TiedUpMod.LOGGER.warn( TiedUpMod.LOGGER.warn(
"V2EquipmentManager: swapping out non-V2 item {} from equipment", "V2EquipmentManager: swapping out non-V2 item {} from equipment",
conflictStack conflictStack

View File

@@ -41,9 +41,10 @@ public class PacketV2SelfLock {
if (player == null) return; if (player == null) return;
if (!PacketRateLimiter.allowPacket(player, "action")) return; if (!PacketRateLimiter.allowPacket(player, "action")) return;
// Arms must be free to self-lock // Arms and hands must be free to self-lock (RISK-006: mittens bypass)
if ( if (
V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS) V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS) ||
V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.HANDS)
) return; ) return;
ItemStack equipped = V2EquipmentHelper.getInRegion( ItemStack equipped = V2EquipmentHelper.getInRegion(

View File

@@ -42,9 +42,10 @@ public class PacketV2SelfUnlock {
if (player == null) return; if (player == null) return;
if (!PacketRateLimiter.allowPacket(player, "action")) return; if (!PacketRateLimiter.allowPacket(player, "action")) return;
// Arms must be free to self-unlock // Arms and hands must be free to self-unlock (RISK-006: mittens bypass)
if ( if (
V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS) V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS) ||
V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.HANDS)
) return; ) return;
ItemStack equipped = V2EquipmentHelper.getInRegion( ItemStack equipped = V2EquipmentHelper.getInRegion(