Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Packet cooldowns and content validation #4782

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
689ca4d
EmoteListPacket RAM Overflow
OurLobanov Jun 17, 2024
70e0361
Update CodecProcessor.java
OurLobanov Jun 17, 2024
575c8fd
EmoteListPacket suk
OurLobanov Jun 17, 2024
a9973e7
text limit TextPacket CommandRequestPacket
OurLobanov Jun 17, 2024
be4a8e6
Against possible leaks memory
OurLobanov Jun 17, 2024
ea6dd5d
Package limits
OurLobanov Jun 17, 2024
74d9a3b
Merge branch 'GeyserMC:master' into exploit
OurLobanov Jun 17, 2024
cea4a42
Fix моей тупости
OurLobanov Jun 20, 2024
2e9e46d
Merge branch 'GeyserMC:master' into exploit
OurLobanov Jun 21, 2024
383d16d
ok
OurLobanov Jun 21, 2024
644cbb0
ResourcePack limit
OurLobanov Jun 21, 2024
fde62f4
soru
OurLobanov Jun 21, 2024
45f0a7f
CommandRequestPacket limit protocol
OurLobanov Jun 22, 2024
3ae2184
Merge branch 'GeyserMC:master' into exploit
OurLobanov Jun 22, 2024
a571ff2
Merge branch 'GeyserMC:master' into exploit
OurLobanov Jun 24, 2024
958d13b
Cooldown fix
OurLobanov Jun 24, 2024
e33e8b5
TextPacket limits in the protocol
OurLobanov Jun 24, 2024
ba8ae5f
Emotes now work
OurLobanov Jun 24, 2024
ac51ec1
b
OurLobanov Jun 26, 2024
c682c5a
п
OurLobanov Jun 26, 2024
d7652bf
hadle RequestNetworkSettingsPacket
OurLobanov Jun 26, 2024
e6ff354
FIXME
OurLobanov Jun 26, 2024
32380c3
add packets
OurLobanov Jun 26, 2024
1ebb1b0
del import
OurLobanov Jun 28, 2024
0f63c26
Merge branch 'GeyserMC:master' into exploit
OurLobanov Jul 4, 2024
c79b9ec
Merge branch 'GeyserMC:master' into exploit
OurLobanov Jul 7, 2024
6e0ded8
import
OurLobanov Jul 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.EmoteFlag;
import org.cloudburstmc.protocol.bedrock.packet.EmotePacket;
import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
Expand Down Expand Up @@ -71,6 +72,8 @@ public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteI
packet.setXuid("");
packet.setPlatformId(""); // BDS sends empty
packet.setEmoteId(emoteId);
packet.getFlags().add(EmoteFlag.SERVER_SIDE);
packet.getFlags().add(EmoteFlag.MUTE_EMOTE_CHAT);
session.sendUpstreamPacket(packet);
}

Expand Down
153 changes: 103 additions & 50 deletions core/src/main/java/org/geysermc/geyser/network/CodecProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,52 +33,21 @@
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.MobEquipmentSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.PlayerHotbarSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityLinkSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.SetEntityMotionSerializer_v291;
import org.cloudburstmc.protocol.bedrock.codec.v390.serializer.PlayerSkinSerializer_v390;
import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventoryContentSerializer_v407;
import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventorySlotSerializer_v407;
import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.ItemStackRequestSerializer_v407;
import org.cloudburstmc.protocol.bedrock.codec.v486.serializer.BossEventSerializer_v486;
import org.cloudburstmc.protocol.bedrock.codec.v554.serializer.TextSerializer_v554;
import org.cloudburstmc.protocol.bedrock.codec.v557.serializer.SetEntityDataSerializer_v557;
import org.cloudburstmc.protocol.bedrock.codec.v567.serializer.CommandRequestSerializer_v567;
import org.cloudburstmc.protocol.bedrock.codec.v630.serializer.SetPlayerInventoryOptionsSerializer_v360;
import org.cloudburstmc.protocol.bedrock.codec.v662.serializer.SetEntityMotionSerializer_v662;
import org.cloudburstmc.protocol.bedrock.packet.AnvilDamagePacket;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.BossEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientCacheBlobStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientCacheStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientCheatAbilityPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientToServerHandshakePacket;
import org.cloudburstmc.protocol.bedrock.packet.CodeBuilderSourcePacket;
import org.cloudburstmc.protocol.bedrock.packet.CraftingEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.CreatePhotoPacket;
import org.cloudburstmc.protocol.bedrock.packet.DebugInfoPacket;
import org.cloudburstmc.protocol.bedrock.packet.EditorNetworkPacket;
import org.cloudburstmc.protocol.bedrock.packet.EntityFallPacket;
import org.cloudburstmc.protocol.bedrock.packet.GameTestRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.cloudburstmc.protocol.bedrock.packet.LabTablePacket;
import org.cloudburstmc.protocol.bedrock.packet.MapCreateLockedCopyPacket;
import org.cloudburstmc.protocol.bedrock.packet.MapInfoRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.MobArmorEquipmentPacket;
import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
import org.cloudburstmc.protocol.bedrock.packet.MultiplayerSettingsPacket;
import org.cloudburstmc.protocol.bedrock.packet.NpcRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.PhotoInfoRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.PhotoTransferPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerHotbarPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket;
import org.cloudburstmc.protocol.bedrock.packet.PurchaseReceiptPacket;
import org.cloudburstmc.protocol.bedrock.packet.RefreshEntitlementsPacket;
import org.cloudburstmc.protocol.bedrock.packet.ScriptMessagePacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
import org.cloudburstmc.protocol.bedrock.packet.SettingsCommandPacket;
import org.cloudburstmc.protocol.bedrock.packet.SimpleEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.SubChunkRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.SubClientLoginPacket;
import org.cloudburstmc.protocol.bedrock.packet.TickSyncPacket;
import org.cloudburstmc.protocol.bedrock.codec.v685.serializer.TextSerializer_v685;
import org.cloudburstmc.protocol.bedrock.data.inventory.InventoryLayout;
import org.cloudburstmc.protocol.bedrock.data.inventory.InventoryTabLeft;
import org.cloudburstmc.protocol.bedrock.data.inventory.InventoryTabRight;
import org.cloudburstmc.protocol.bedrock.packet.*;
OurLobanov marked this conversation as resolved.
Show resolved Hide resolved
import org.cloudburstmc.protocol.common.util.VarInts;

/**
Expand Down Expand Up @@ -136,6 +105,87 @@ public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlot
}
};


/**
* The player can cause a packet error themselves, which hackers can exploit to spam legitimate errors
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment. We now disconnect on the first error for any packet. Regardless, this seems like it should be a change in protocol, not Geyser. Changes in here should only be things we can do as a result of fields we do not need in Geyser.

*/
private static final BedrockPacketSerializer<SetPlayerInventoryOptionsPacket> SET_PLAYER_INVENTORY_OPTIONS_SERIALIZER = new SetPlayerInventoryOptionsSerializer_v360() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetPlayerInventoryOptionsPacket packet) {
int leftTabIndex = VarInts.readInt(buffer);
int rightTabIndex = VarInts.readInt(buffer);

packet.setLeftTab(leftTabIndex >= 0 && leftTabIndex < InventoryTabLeft.VALUES.length ? InventoryTabLeft.VALUES[leftTabIndex] : InventoryTabLeft.NONE);
packet.setRightTab(rightTabIndex >= 0 && rightTabIndex < InventoryTabRight.VALUES.length ? InventoryTabRight.VALUES[rightTabIndex] : InventoryTabRight.NONE);

packet.setFiltering(buffer.readBoolean());

int layoutIndex = VarInts.readInt(buffer);
packet.setLayout(layoutIndex >= 0 && layoutIndex < InventoryLayout.VALUES.length ? InventoryLayout.VALUES[layoutIndex] : InventoryLayout.NONE);

int craftingLayoutIndex = VarInts.readInt(buffer);
packet.setCraftingLayout(craftingLayoutIndex >= 0 && craftingLayoutIndex < InventoryLayout.VALUES.length ? InventoryLayout.VALUES[craftingLayoutIndex] : InventoryLayout.NONE);
}
};

private static final BedrockPacketSerializer<ItemStackRequestPacket> ITEM_STACK_REQUEST_SERIALIZER = new ItemStackRequestSerializer_v407() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, ItemStackRequestPacket packet) {
helper.readArray(buffer, packet.getRequests(), helper::readItemStackRequest, 110); // 64 is NOT enough, cloudburst
OurLobanov marked this conversation as resolved.
Show resolved Hide resolved
}
};

private static final BedrockPacketSerializer<CommandRequestPacket> COMMAND_REQUEST_SERIALIZER = new CommandRequestSerializer_v567() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, CommandRequestPacket packet) {
packet.setCommand(helper.readStringMaxLen(buffer, 513));
packet.setCommandOriginData(helper.readCommandOrigin(buffer));
packet.setInternal(buffer.readBoolean());
packet.setVersion(VarInts.readInt(buffer));
}
};

private static final BedrockPacketSerializer<TextPacket> TEXT_SERIALIZER_V554 = new TextSerializer_v554() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, TextPacket packet) {
TextPacket.Type type = TextPacket.Type.values()[buffer.readUnsignedByte()];
packet.setType(type);
packet.setNeedsTranslation(buffer.readBoolean());

if (type == TextPacket.Type.CHAT) {
packet.setSourceName(helper.readString(buffer));
//The client does not send more than 512 characters, and we do not need to decode other TextPacket.Type
packet.setMessage(helper.readStringMaxLen(buffer, 513));
} else {
throw new IllegalArgumentException("Unsupported TextType " + type);
}

packet.setXuid(helper.readString(buffer));
packet.setPlatformChatId(helper.readString(buffer));
}
};

private static final BedrockPacketSerializer<TextPacket> TEXT_SERIALIZER = new TextSerializer_v685() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, TextPacket packet) {
TextPacket.Type type = TextPacket.Type.values()[buffer.readUnsignedByte()];
packet.setType(type);
packet.setNeedsTranslation(buffer.readBoolean());

if (type == TextPacket.Type.CHAT) {
packet.setSourceName(helper.readString(buffer));
//The client does not send more than 512 characters, and we do not need to decode other TextPacket.Type
packet.setMessage(helper.readStringMaxLen(buffer, 513));
} else {
throw new IllegalArgumentException("Unsupported TextType " + type);
}

packet.setXuid(helper.readString(buffer));
packet.setPlatformChatId(helper.readString(buffer));
packet.setFilteredMessage(helper.readString(buffer));
}
};

/**
* Serializer that does nothing when trying to deserialize BossEventPacket since it is not used from the client.
*/
Expand Down Expand Up @@ -181,15 +231,6 @@ public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityData
}
};

/**
* Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v291.
*/
private static final BedrockPacketSerializer<SetEntityMotionPacket> SET_ENTITY_MOTION_SERIALIZER_V291 = new SetEntityMotionSerializer_v291() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) {
}
};

/**
* Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v662.
*/
Expand Down Expand Up @@ -251,6 +292,7 @@ static BedrockCodec processCodec(BedrockCodec codec) {
.updateSerializer(SettingsCommandPacket.class, IGNORED_SERIALIZER)
.updateSerializer(AnvilDamagePacket.class, IGNORED_SERIALIZER)
.updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER)
.updateSerializer(EmoteListPacket.class, IGNORED_SERIALIZER)
// Illegal when serverbound due to Geyser specific setup
.updateSerializer(InventoryContentPacket.class, INVENTORY_CONTENT_SERIALIZER)
.updateSerializer(InventorySlotPacket.class, INVENTORY_SLOT_SERIALIZER)
Expand All @@ -271,13 +313,24 @@ static BedrockCodec processCodec(BedrockCodec codec) {
// Ignored bidirectional packets
.updateSerializer(ClientCacheStatusPacket.class, IGNORED_SERIALIZER)
.updateSerializer(SimpleEventPacket.class, IGNORED_SERIALIZER)
.updateSerializer(MultiplayerSettingsPacket.class, IGNORED_SERIALIZER);
.updateSerializer(MultiplayerSettingsPacket.class, IGNORED_SERIALIZER)
// Small limit
.updateSerializer(ItemStackRequestPacket.class, ITEM_STACK_REQUEST_SERIALIZER);


if (codec.getProtocolVersion() < 685) {
// Ignored bidirectional packets
codecBuilder.updateSerializer(TickSyncPacket.class, IGNORED_SERIALIZER);
}

codecBuilder.updateSerializer(CommandRequestPacket.class, COMMAND_REQUEST_SERIALIZER);
codecBuilder.updateSerializer(SetPlayerInventoryOptionsPacket.class, SET_PLAYER_INVENTORY_OPTIONS_SERIALIZER);
if (codec.getProtocolVersion() >= 685) {
codecBuilder.updateSerializer(TextPacket.class, TEXT_SERIALIZER);
} else {
codecBuilder.updateSerializer(TextPacket.class, TEXT_SERIALIZER_V554);
}

return codecBuilder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,26 @@
public class LoggingPacketHandler implements BedrockPacketHandler {
protected final GeyserImpl geyser;
protected final GeyserSession session;
protected final PacketCooldownManager cooldownHandler;

LoggingPacketHandler(GeyserImpl geyser, GeyserSession session) {
this.geyser = geyser;
this.session = session;
this.cooldownHandler = new PacketCooldownManager(session, 1000);
}

public boolean handleLimit(BedrockPacket packet) {
boolean safePacket = this.cooldownHandler.handle(packet);
if (!safePacket) {
session.disconnect("many Packets " + packet.getClass().getSimpleName());
}
return !safePacket;
}

PacketSignal defaultHandler(BedrockPacket packet) {
if (handleLimit(packet)) {
return PacketSignal.HANDLED;
}
geyser.getLogger().debug("Handled packet: " + packet.getClass().getSimpleName());
return PacketSignal.HANDLED;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.geysermc.geyser.network;

import lombok.Getter;
import lombok.Setter;
import org.cloudburstmc.protocol.bedrock.packet.*;
import org.geysermc.geyser.session.GeyserSession;

import java.util.HashMap;
import java.util.Map;

public class PacketCooldownManager {
private final Map<String, CooldownSettings> packetCooldownSettings = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use fastutil Object2ObjectMap.


private final GeyserSession session;
@Setter
private long cooldownMillisDebug;
private long expiryTimeMillisDebug;

public PacketCooldownManager(GeyserSession session, long cooldownMillisDebug) {
this.session = session;
this.setCooldownMillisDebug(cooldownMillisDebug);
this.expiryTimeMillisDebug = 0;
OurLobanov marked this conversation as resolved.
Show resolved Hide resolved

setPacketCooldown(LoginPacket.class, -1, 2);
setPacketCooldown(ResourcePackClientResponsePacket.class, -1, 4);
setPacketCooldown(ResourcePackChunkRequestPacket.class, -1, 0);
setPacketCooldown(TextPacket.class, 1000, 50);
setPacketCooldown(CommandRequestPacket.class, 1000, 50);
setPacketCooldown(ModalFormResponsePacket.class, 1000, 50);
OurLobanov marked this conversation as resolved.
Show resolved Hide resolved
}

public void setPacketCooldown(Class<? extends BedrockPacket> packetClass, int cooldownMillis, int maxCount) {
packetCooldownSettings.put(packetClass.getSimpleName(), new CooldownSettings(cooldownMillis, maxCount));
}

private final Map<String, CooldownTracker> activeCooldowns = new HashMap<>();

private boolean isCooldownActive(BedrockPacket packet) {
String packetName = packet.getClass().getSimpleName();
CooldownTracker tracker = activeCooldowns.get(packetName);
if (tracker != null && tracker.getCount() >= packetCooldownSettings.get(packetName).maxCount()) {
if (tracker.getExpiryTime() != -1 && tracker.getExpiryTime() <= System.currentTimeMillis()) {
activeCooldowns.remove(packetName);
} else {
return true;
}
}
return false;
}

private void updateCooldown(BedrockPacket packet) {
String packetName = packet.getClass().getSimpleName();
CooldownSettings settings = packetCooldownSettings.get(packetName);
CooldownTracker tracker = activeCooldowns.computeIfAbsent(packetName, k -> {
CooldownTracker newTracker = new CooldownTracker();
long cooldownMillis = settings.cooldownMillis();
if (cooldownMillis == -1) {
newTracker.setExpiryTime(-1);
} else {
newTracker.setExpiryTime(System.currentTimeMillis() + cooldownMillis);
}
return newTracker;
});
tracker.incrementCount();
}

public boolean handle(BedrockPacket packet) {
String packetName = packet.getClass().getSimpleName();
if (packetCooldownSettings.containsKey(packetName)) {
updateCooldown(packet);
if (isCooldownActive(packet)) {
if (expiryTimeMillisDebug <= System.currentTimeMillis()) {
CooldownTracker tracker = activeCooldowns.get(packetName);
String message = session.getSocketAddress().getAddress().toString() + " -> Attempted to send too many packets " + packet.getClass().getSimpleName() + " count " + tracker.getCount();
if (session.isLoggedIn()) {
message += " by user " + session.bedrockUsername();
}
session.getGeyser().getLogger().debug(message);
}
this.expiryTimeMillisDebug = System.currentTimeMillis() + cooldownMillisDebug;
return false;
}
}
return true;
}
OurLobanov marked this conversation as resolved.
Show resolved Hide resolved

private record CooldownSettings(int cooldownMillis, int maxCount) {
}

@Getter
private class CooldownTracker {
private long count;
@Setter
private long expiryTime;

public void incrementCount() {
this.count++;
}
}
}
Loading