You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
fix: wired double-fire guard, RoomUnit path race, roomItems iteration, Netty CVE
Continuation of the concurrency hardening from the audit: - InteractionWired/WiredHandler (E4): add an atomic per-box processing guard so one trigger box is handled by a single thread at a time, making the cooldown check-and-set effectively atomic; mark `cooldown` volatile. Prevents a packet thread and the room cycle thread from double-firing the same wired stack (double teleport/reward). - RoomUnit (C1): the walk path is now a volatile ConcurrentLinkedDeque instead of a plain LinkedList, so the room cycle popping steps can't corrupt it while a walk packet rebuilds it via findPath/setPath. - RoomItemManager (C2): iterate roomItems under its own monitor in getFloorItems/ getWallItems/getPostItNotes/getUserUniqueFurniCount/getItemsAt, matching the existing put/remove sync sites — stops place/pickup from corrupting the traversal into a silently-incomplete item set. - pom.xml (S4): bump netty-all 4.1.115 -> 4.1.118.Final (CVE-2025-24970 SslHandler pre-auth DoS, CVE-2025-25193).
This commit is contained in:
+1
-1
@@ -83,7 +83,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.netty</groupId>
|
<groupId>io.netty</groupId>
|
||||||
<artifactId>netty-all</artifactId>
|
<artifactId>netty-all</artifactId>
|
||||||
<version>4.1.115.Final</version>
|
<version>4.1.118.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- GSON -->
|
<!-- GSON -->
|
||||||
|
|||||||
+15
-1
@@ -18,6 +18,7 @@ import java.sql.SQLException;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base abstract class for all wired furniture items (triggers, effects, conditions, extras).
|
* Base abstract class for all wired furniture items (triggers, effects, conditions, extras).
|
||||||
@@ -61,7 +62,11 @@ public abstract class InteractionWired extends InteractionDefault {
|
|||||||
*/
|
*/
|
||||||
private static final long CACHE_EXPIRY_MS = 5 * 60 * 1000;
|
private static final long CACHE_EXPIRY_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
private long cooldown;
|
private volatile long cooldown;
|
||||||
|
// Ensures one box is processed by a single thread at a time, so the
|
||||||
|
// cooldown check-and-set in WiredHandler can't double-fire when a packet
|
||||||
|
// thread and the room cycle thread trigger the same box concurrently.
|
||||||
|
private final AtomicBoolean processing = new AtomicBoolean(false);
|
||||||
private final ConcurrentHashMap<Long, Long> userExecutionCache = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<Long, Long> userExecutionCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
InteractionWired(ResultSet set, Item baseItem) throws SQLException {
|
InteractionWired(ResultSet set, Item baseItem) throws SQLException {
|
||||||
@@ -149,6 +154,15 @@ public abstract class InteractionWired extends InteractionDefault {
|
|||||||
this.cooldown = newMillis;
|
this.cooldown = newMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Claims exclusive processing of this box; returns false if another thread is already in it. */
|
||||||
|
public boolean tryBeginProcessing() {
|
||||||
|
return this.processing.compareAndSet(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endProcessing() {
|
||||||
|
this.processing.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean allowWiredResetState() {
|
public boolean allowWiredResetState() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -167,17 +167,22 @@ public class RoomItemManager {
|
|||||||
*/
|
*/
|
||||||
public THashSet<HabboItem> getFloorItems() {
|
public THashSet<HabboItem> getFloorItems() {
|
||||||
THashSet<HabboItem> items = new THashSet<>();
|
THashSet<HabboItem> items = new THashSet<>();
|
||||||
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
// roomItems is a TCollections.synchronizedMap; its iterator is not safe
|
||||||
|
// against concurrent put/remove (item place/pickup), so hold the map
|
||||||
|
// monitor for the whole traversal, matching the mutation sites.
|
||||||
|
synchronized (this.roomItems) {
|
||||||
|
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
||||||
|
|
||||||
for (int i = this.roomItems.size(); i-- > 0; ) {
|
for (int i = this.roomItems.size(); i-- > 0; ) {
|
||||||
try {
|
try {
|
||||||
iterator.advance();
|
iterator.advance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iterator.value().getBaseItem().getType() == FurnitureType.FLOOR) {
|
if (iterator.value().getBaseItem().getType() == FurnitureType.FLOOR) {
|
||||||
items.add(iterator.value());
|
items.add(iterator.value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,17 +194,19 @@ public class RoomItemManager {
|
|||||||
*/
|
*/
|
||||||
public THashSet<HabboItem> getWallItems() {
|
public THashSet<HabboItem> getWallItems() {
|
||||||
THashSet<HabboItem> items = new THashSet<>();
|
THashSet<HabboItem> items = new THashSet<>();
|
||||||
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
synchronized (this.roomItems) {
|
||||||
|
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
||||||
|
|
||||||
for (int i = this.roomItems.size(); i-- > 0; ) {
|
for (int i = this.roomItems.size(); i-- > 0; ) {
|
||||||
try {
|
try {
|
||||||
iterator.advance();
|
iterator.advance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iterator.value().getBaseItem().getType() == FurnitureType.WALL) {
|
if (iterator.value().getBaseItem().getType() == FurnitureType.WALL) {
|
||||||
items.add(iterator.value());
|
items.add(iterator.value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,18 +218,20 @@ public class RoomItemManager {
|
|||||||
*/
|
*/
|
||||||
public THashSet<HabboItem> getPostItNotes() {
|
public THashSet<HabboItem> getPostItNotes() {
|
||||||
THashSet<HabboItem> items = new THashSet<>();
|
THashSet<HabboItem> items = new THashSet<>();
|
||||||
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
synchronized (this.roomItems) {
|
||||||
|
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
||||||
|
|
||||||
for (int i = this.roomItems.size(); i-- > 0; ) {
|
for (int i = this.roomItems.size(); i-- > 0; ) {
|
||||||
try {
|
try {
|
||||||
iterator.advance();
|
iterator.advance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iterator.value().getBaseItem().getInteractionType().getType()
|
if (iterator.value().getBaseItem().getInteractionType().getType()
|
||||||
== InteractionPostIt.class) {
|
== InteractionPostIt.class) {
|
||||||
items.add(iterator.value());
|
items.add(iterator.value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,44 +285,49 @@ public class RoomItemManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
// Cache miss: iterate roomItems under its monitor so a concurrent
|
||||||
|
// place/pickup can't rehash the map mid-traversal (which the per-advance
|
||||||
|
// try/catch would otherwise silently swallow into an incomplete result).
|
||||||
|
synchronized (this.roomItems) {
|
||||||
|
TIntObjectIterator<HabboItem> iterator = this.roomItems.iterator();
|
||||||
|
|
||||||
for (int i = this.roomItems.size(); i-- > 0; ) {
|
for (int i = this.roomItems.size(); i-- > 0; ) {
|
||||||
HabboItem item;
|
HabboItem item;
|
||||||
try {
|
try {
|
||||||
iterator.advance();
|
iterator.advance();
|
||||||
item = iterator.value();
|
item = iterator.value();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.getBaseItem().getType() != FurnitureType.FLOOR) {
|
if (item.getBaseItem().getType() != FurnitureType.FLOOR) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int width, length;
|
int width, length;
|
||||||
|
|
||||||
if (item.getRotation() != 2 && item.getRotation() != 6) {
|
if (item.getRotation() != 2 && item.getRotation() != 6) {
|
||||||
width = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1;
|
width = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1;
|
||||||
length = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1;
|
length = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1;
|
||||||
} else {
|
} else {
|
||||||
width = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1;
|
width = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1;
|
||||||
length = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1;
|
length = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(tile.x >= item.getX() && tile.x <= item.getX() + width - 1 && tile.y >= item.getY()
|
if (!(tile.x >= item.getX() && tile.x <= item.getX() + width - 1 && tile.y >= item.getY()
|
||||||
&& tile.y <= item.getY() + length - 1)) {
|
&& tile.y <= item.getY() + length - 1)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add(item);
|
items.add(item);
|
||||||
|
|
||||||
if (returnOnFirst) {
|
if (returnOnFirst) {
|
||||||
return items;
|
return items;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,9 +970,11 @@ public class RoomItemManager {
|
|||||||
public int getUserUniqueFurniCount(int userId) {
|
public int getUserUniqueFurniCount(int userId) {
|
||||||
THashSet<Item> items = new THashSet<>();
|
THashSet<Item> items = new THashSet<>();
|
||||||
|
|
||||||
for (HabboItem item : this.roomItems.valueCollection()) {
|
synchronized (this.roomItems) {
|
||||||
if (!items.contains(item.getBaseItem()) && item.getUserId() == userId) {
|
for (HabboItem item : this.roomItems.valueCollection()) {
|
||||||
items.add(item.getBaseItem());
|
if (!items.contains(item.getBaseItem()) && item.getUserId() == userId) {
|
||||||
|
items.add(item.getBaseItem());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -71,7 +72,10 @@ public class RoomUnit {
|
|||||||
private RoomUserRotation headRotation = RoomUserRotation.NORTH;
|
private RoomUserRotation headRotation = RoomUserRotation.NORTH;
|
||||||
private DanceType danceType;
|
private DanceType danceType;
|
||||||
private RoomUnitType roomUnitType;
|
private RoomUnitType roomUnitType;
|
||||||
private Deque<RoomTile> path = new LinkedList<>();
|
// Concurrent + volatile: the room cycle thread polls/clears this path while a
|
||||||
|
// walk packet thread rebuilds it via findPath/setPath. A plain LinkedList would
|
||||||
|
// corrupt under the concurrent structural modification.
|
||||||
|
private volatile Deque<RoomTile> path = new ConcurrentLinkedDeque<>();
|
||||||
private int handItem;
|
private int handItem;
|
||||||
private long handItemTimestamp;
|
private long handItemTimestamp;
|
||||||
private long lastRollerTime;
|
private long lastRollerTime;
|
||||||
@@ -587,7 +591,7 @@ public class RoomUnit {
|
|||||||
Deque<RoomTile> newPath = this.room.getLayout().getPathfinder()
|
Deque<RoomTile> newPath = this.room.getLayout().getPathfinder()
|
||||||
.findPath(this.currentLocation, this.goalLocation, this.goalLocation, this);
|
.findPath(this.currentLocation, this.goalLocation, this.goalLocation, this);
|
||||||
if (newPath != null && !newPath.isEmpty()) {
|
if (newPath != null && !newPath.isEmpty()) {
|
||||||
this.path = newPath;
|
this.path = new ConcurrentLinkedDeque<>(newPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,7 +769,7 @@ public class RoomUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setPath(Deque<RoomTile> path) {
|
public void setPath(Deque<RoomTile> path) {
|
||||||
this.path = path;
|
this.path = (path == null) ? new ConcurrentLinkedDeque<>() : new ConcurrentLinkedDeque<>(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoomRightLevels getRightsLevel() {
|
public RoomRightLevels getRightsLevel() {
|
||||||
|
|||||||
@@ -178,6 +178,15 @@ public class WiredHandler {
|
|||||||
private static boolean handle(InteractionWiredTrigger trigger, final RoomUnit roomUnit, final Room room, final Object[] stuff, final LegacyExecutionPlan executionPlan) {
|
private static boolean handle(InteractionWiredTrigger trigger, final RoomUnit roomUnit, final Room room, final Object[] stuff, final LegacyExecutionPlan executionPlan) {
|
||||||
long millis = System.currentTimeMillis();
|
long millis = System.currentTimeMillis();
|
||||||
int roomUnitId = roomUnit != null ? roomUnit.getId() : -1;
|
int roomUnitId = roomUnit != null ? roomUnit.getId() : -1;
|
||||||
|
|
||||||
|
// Only one thread may process a given trigger box at a time, so the
|
||||||
|
// cooldown check (below) and setCooldown (further down) act as one
|
||||||
|
// atomic claim — preventing a concurrent packet/cycle double-fire.
|
||||||
|
if (!trigger.tryBeginProcessing()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (Emulator.isReady && ((Emulator.getConfig().getBoolean("wired.custom.enabled", false) && (trigger.canExecute(millis) || roomUnitId > -1) && trigger.userCanExecute(roomUnitId, millis)) || (!Emulator.getConfig().getBoolean("wired.custom.enabled", false) && trigger.canExecute(millis))) && trigger.execute(roomUnit, room, stuff)) {
|
if (Emulator.isReady && ((Emulator.getConfig().getBoolean("wired.custom.enabled", false) && (trigger.canExecute(millis) || roomUnitId > -1) && trigger.userCanExecute(roomUnitId, millis)) || (!Emulator.getConfig().getBoolean("wired.custom.enabled", false) && trigger.canExecute(millis))) && trigger.execute(roomUnit, room, stuff)) {
|
||||||
THashSet<InteractionWiredCondition> conditions = room.getRoomSpecialTypes().getConditions(trigger.getX(), trigger.getY());
|
THashSet<InteractionWiredCondition> conditions = room.getRoomSpecialTypes().getConditions(trigger.getX(), trigger.getY());
|
||||||
THashSet<InteractionWiredEffect> effects = room.getRoomSpecialTypes().getEffects(trigger.getX(), trigger.getY());
|
THashSet<InteractionWiredEffect> effects = room.getRoomSpecialTypes().getEffects(trigger.getX(), trigger.getY());
|
||||||
@@ -272,6 +281,9 @@ public class WiredHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
|
trigger.endProcessing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean evaluateConditions(THashSet<InteractionWiredCondition> conditions, RoomUnit roomUnit, Room room, Object[] stuff, int evaluationMode, int evaluationValue) {
|
private static boolean evaluateConditions(THashSet<InteractionWiredCondition> conditions, RoomUnit roomUnit, Room room, Object[] stuff, int evaluationMode, int evaluationValue) {
|
||||||
|
|||||||
Reference in New Issue
Block a user