From 3383c630e332298a35ccd0209da3b6683fc28fec Mon Sep 17 00:00:00 2001 From: punchready Date: Mon, 25 Jul 2022 11:29:25 +0200 Subject: [PATCH 1/8] Strictly filter tile types in send tile rect handling --- TShockAPI/Handlers/SendTileRectHandler.cs | 73 ++++++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index b5c0bafd5..3a3132990 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -145,14 +145,68 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle int objHeight = data.Height; int offsetY = 0; - if (newTile.Type == TileID.TrapdoorClosed) + // Verify that the changes are actually valid conceptually + if (Main.tile[realX, realY].type == newTile.Type) { - // Trapdoors can modify a 2x3 space. When it closes it will have leftover tiles either on top or bottom. - // If we don't update these tiles, the trapdoor gets confused and disappears. - // So we capture all 6 possible tiles and offset ourselves 1 tile above the closed trapdoor to capture the entire 2x3 area - objWidth = 2; - objHeight = 3; - offsetY = -1; + switch (newTile.Type) + { + // Some individual cases might still allow crashing exploits, as the actual framing is not being checked here + // Doing so requires hard-coding the individual valid framing values and is a lot of effort + case TileID.ProjectilePressurePad: + case TileID.WirePipe: + case TileID.Traps: + case TileID.Candles: + case TileID.PeaceCandle: + case TileID.WaterCandle: + case TileID.PlatinumCandle: + case TileID.Chairs: + case TileID.Bathtubs: + case TileID.Beds: + case TileID.Firework: + case TileID.WaterFountain: + case TileID.BloodMoonMonolith: + case TileID.VoidMonolith: + case TileID.LunarMonolith: + case TileID.MusicBoxes: + case TileID.ArrowSign: + case TileID.PaintedArrowSign: + case TileID.Cannon: + case TileID.Campfire: + case TileID.Plants: + case TileID.MinecartTrack: + case TileID.ChristmasTree: + { + // allowed changes + } + break; + default: + { + continue; + } + } + } + else + { + // Together with Flower Boots and Land Mine destruction, these are the only cases where a tile type is allowed to be modified + switch (newTile.Type) + { + case TileID.LogicSensor: + case TileID.FoodPlatter: + case TileID.WeaponsRack2: + case TileID.ItemFrame: + case TileID.HatRack: + case TileID.DisplayDoll: + case TileID.TeleportationPylon: + case TileID.TargetDummy: + { + // allowed placements + } + break; + default: + { + continue; + } + } } // Ensure the tile object fits inside the rect before processing it @@ -249,7 +303,10 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect UpdateServerTileState(tile, newTile, TileDataType.Tile); } - ProcessConversionSpreads(Main.tile[realX, realY], newTile); + if (rectWidth == 1 && rectLength == 1) // Conversion only sends a 1x1 rect + { + ProcessConversionSpreads(Main.tile[realX, realY], newTile); + } // All other single tile updates should not be processed. } From abaf41452341fffe8cd9d1b09bbfd39b510ea0b4 Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 26 Jul 2022 00:01:25 +0200 Subject: [PATCH 2/8] Remove tiles which are no longer sent in this packet --- TShockAPI/Handlers/SendTileRectHandler.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 3a3132990..564590fda 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -159,9 +159,6 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle case TileID.PeaceCandle: case TileID.WaterCandle: case TileID.PlatinumCandle: - case TileID.Chairs: - case TileID.Bathtubs: - case TileID.Beds: case TileID.Firework: case TileID.WaterFountain: case TileID.BloodMoonMonolith: From 681c6de1f7f4108d97d8d99433e21960eec361fd Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 26 Jul 2022 08:24:46 +0200 Subject: [PATCH 3/8] add strict STR size checking --- TShockAPI/Configuration/TShockConfig.cs | 8 -------- TShockAPI/Handlers/SendTileRectHandler.cs | 14 ++++---------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/TShockAPI/Configuration/TShockConfig.cs b/TShockAPI/Configuration/TShockConfig.cs index d87dfa227..8a5e3321c 100644 --- a/TShockAPI/Configuration/TShockConfig.cs +++ b/TShockAPI/Configuration/TShockConfig.cs @@ -445,14 +445,6 @@ public class TShockSettings [Description("Whether or not to kick users when they surpass the HealOther threshold.")] public bool KickOnHealOtherThresholdBroken = false; - /// Disables a player if this number of tiles is present in a Tile Rectangle packet - [Description("Disables a player if this number of tiles is present in a Tile Rectangle packet")] - public int TileRectangleSizeThreshold = 50; - - /// Whether or not to kick users when they surpass the TileRectangleSize threshold. - [Description("Whether or not to kick users when they surpass the TileRectangleSize threshold.")] - public bool KickOnTileRectangleSizeThresholdBroken = false; - /// Whether or not the server should suppress build permission failure warnings from regions, spawn point, or server edit failure. [Description("Whether or not the server should suppress build permission failure warnings from regions, spawn point, or server edit failure.")] public bool SuppressPermissionFailureNotices = false; diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 564590fda..8f6c33c84 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -232,7 +232,7 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle } } } - + /// /// Processes a tile object consisting of multiple tiles from the tile rect packet /// @@ -290,12 +290,12 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect ITile tile = Main.tile[realX, realY]; - if (tile.type == TileID.LandMine && !newTile.Active) + if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.LandMine && !newTile.Active) { UpdateServerTileState(tile, newTile, TileDataType.Tile); } - if (tile.type == TileID.WirePipe) + if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.WirePipe) { UpdateServerTileState(tile, newTile, TileDataType.Tile); } @@ -500,15 +500,9 @@ static bool ShouldSkipProcessing(GetDataHandlers.SendTileRectEventArgs args) return true; } - var rectSize = args.Width * args.Length; - if (rectSize > TShock.Config.Settings.TileRectangleSizeThreshold) + if (args.Width > 4 || args.Length > 4) // as of 1.4.3.6 this is the biggest size the client will send in any case { TShock.Log.ConsoleDebug("Bouncer / SendTileRect rejected from non-vanilla tilemod from {0}", args.Player.Name); - if (TShock.Config.Settings.KickOnTileRectangleSizeThresholdBroken) - { - args.Player.Kick("Unexpected tile threshold reached"); - } - return true; } From 82a095f3cf8bd66d30c727d242c79edc98be8561 Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 26 Jul 2022 08:58:56 +0200 Subject: [PATCH 4/8] fix a region bypass exploit using the ice rod --- TShockAPI/Bouncer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index dae664610..736fc6d79 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -401,6 +401,18 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) return; } + // i do not understand the ice tile check enough to be able to modify it, however i do know that it can be used to completely bypass region protection + // this check ensures that build permission is always checked no matter what + if (!args.Player.HasBuildPermission(tileX, tileY)) + { + TShock.Log.ConsoleDebug("Bouncer / OnTileEdit rejected from build from {0} {1} {2}", args.Player.Name, action, editData); + + GetRollbackRectSize(tileX, tileY, out byte width, out byte length, out int offsetY); + args.Player.SendTileRect((short)(tileX - width), (short)(tileY + offsetY), (byte)(width * 2), (byte)(length + 1)); + args.Handled = true; + return; + } + if (editData < 0 || ((action == EditAction.PlaceTile || action == EditAction.ReplaceTile) && editData >= Main.maxTileSets) || ((action == EditAction.PlaceWall || action == EditAction.ReplaceWall) && editData >= Main.maxWallTypes)) From 8b4dc3a1a40b0f82ce2af07af76094e153787184 Mon Sep 17 00:00:00 2001 From: punchready Date: Wed, 27 Jul 2022 06:23:34 +0200 Subject: [PATCH 5/8] Fix flower boots check, clean up code --- TShockAPI/Handlers/SendTileRectHandler.cs | 111 +++++++++++++--------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 8f6c33c84..7d073266c 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -19,14 +19,45 @@ namespace TShockAPI.Handlers /// public class SendTileRectHandler : IPacketHandler { - /// - /// Maps grass-type blocks to flowers that can be grown on them with flower boots - /// - public static Dictionary> GrassToPlantMap = new Dictionary> + private static readonly Dictionary> PlantToGrassMap = new Dictionary> { - { TileID.Grass, new List { TileID.Plants, TileID.Plants2 } }, - { TileID.HallowedGrass, new List { TileID.HallowedPlants, TileID.HallowedPlants2 } }, - { TileID.JungleGrass, new List { TileID.JunglePlants, TileID.JunglePlants2 } } + { TileID.Plants, new HashSet() + { + TileID.Grass, TileID.GolfGrass + } }, + { TileID.HallowedPlants, new HashSet() + { + TileID.HallowedGrass, TileID.GolfGrassHallowed + } }, + { TileID.HallowedPlants2, new HashSet() + { + TileID.HallowedGrass, TileID.GolfGrassHallowed + } }, + { TileID.JunglePlants2, new HashSet() + { + TileID.JungleGrass + } }, + }; + + private static readonly Dictionary> PlantToStyleMap = new Dictionary>() + { + { TileID.Plants, new HashSet() + { + 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 27, 30, 33, 36, 39, 42, + 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, + } }, + { TileID.HallowedPlants, new HashSet() + { + 4, 6, + } }, + { TileID.HallowedPlants2, new HashSet() + { + 2, 3, 4, 6, 7, + } }, + { TileID.JunglePlants2, new HashSet() + { + 9, 10, 11, 12, 13, 14, 15, 16, + } }, }; /// @@ -40,7 +71,7 @@ public class SendTileRectHandler : IPacketHandler /// Maps TileIDs to Tile Entity IDs. - /// Note: is empty at the time of writing, but entities are dynamically assigned their ID at initialize time + /// Note: is empty at the time of writing, but entities are dynamically assigned their ID at initialize time /// which is why we can use the _myEntityId field on each entity type /// public static Dictionary TileEntityIdToTileIdMap = new Dictionary @@ -139,13 +170,8 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle // and process them as a tile object if (newTile.Type < TileObjectData._data.Count && TileObjectData._data[newTile.Type] != null) { - data = TileObjectData._data[newTile.Type]; - NetTile[,] newTiles; - int objWidth = data.Width; - int objHeight = data.Height; - int offsetY = 0; - // Verify that the changes are actually valid conceptually + // Many tiles that are never placed or modified using this packet are valid TileObjectData entries, which is the main attack vector for most exploits using this packet if (Main.tile[realX, realY].type == newTile.Type) { switch (newTile.Type) @@ -206,8 +232,13 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle } } + data = TileObjectData._data[newTile.Type]; + NetTile[,] newTiles; + int objWidth = data.Width; + int objHeight = data.Height; + // Ensure the tile object fits inside the rect before processing it - if (!DoesTileObjectFitInTileRect(x, y, objWidth, objHeight, width, length, offsetY, processed)) + if (!DoesTileObjectFitInTileRect(x, y, objWidth, objHeight, width, length, processed)) { continue; } @@ -218,11 +249,11 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle { for (int j = 0; j < objHeight; j++) { - newTiles[i, j] = tiles[x + i, y + j + offsetY]; - processed[x + i, y + j + offsetY] = true; + newTiles[i, j] = tiles[x + i, y + j]; + processed[x + i, y + j] = true; } } - ProcessTileObject(newTile.Type, realX, realY + offsetY, objWidth, objHeight, newTiles, args); + ProcessTileObject(newTile.Type, realX, realY, objWidth, objHeight, newTiles, args); continue; } @@ -282,9 +313,9 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect { // Some boots allow growing flowers on grass. This process sends a 1x1 tile rect to grow the flowers // The rect size must be 1 and the player must have an accessory that allows growing flowers in order for this rect to be valid - if (rectWidth == 1 && rectLength == 1 && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type))) + if (rectWidth == 1 && rectLength == 1 && WorldGen.InWorld(realX, realY + 1) && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type))) { - ProcessFlowerBoots(realX, realY, newTile, args); + ProcessFlowerBoots(realX, realY, newTile); return; } @@ -302,7 +333,7 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect if (rectWidth == 1 && rectLength == 1) // Conversion only sends a 1x1 rect { - ProcessConversionSpreads(Main.tile[realX, realY], newTile); + ProcessConversionSpreads(tile, newTile); } // All other single tile updates should not be processed. @@ -314,24 +345,18 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect /// The tile x position of the tile rect packet - this is where the flowers are intending to grow /// The tile y position of the tile rect packet - this is where the flowers are intending to grow /// The NetTile containing information about the flowers that are being grown - /// SendTileRectEventArgs containing event information - internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile, GetDataHandlers.SendTileRectEventArgs args) + internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile) { - // We need to get the tile below the tile rect to determine what grass types are allowed - if (!WorldGen.InWorld(realX, realY + 1)) - { - // If the tile below the tile rect isn't valid, we return here and don't update the server tile state - return; - } - - ITile tile = Main.tile[realX, realY + 1]; - if (!GrassToPlantMap.TryGetValue(tile.type, out List plantTiles) && !plantTiles.Contains(newTile.Type)) + ITile tile = Main.tile[realX, realY]; + // Ensure that the placed plant is valid for the grass below, that the target tile is empty, and that the placed plant has valid framing + if ( + PlantToGrassMap.TryGetValue(newTile.Type, out HashSet grassTiles) && + !tile.active() && grassTiles.Contains(Main.tile[realX, realY + 1].type) && + PlantToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18)) + ) { - // If the tile below the tile rect isn't a valid plant tile (eg grass) then we don't update the server tile state - return; + UpdateServerTileState(tile, newTile, TileDataType.Tile); } - - UpdateServerTileState(Main.tile[realX, realY], newTile, TileDataType.Tile); } /// @@ -532,24 +557,16 @@ static bool ShouldSkipProcessing(GetDataHandlers.SendTileRectEventArgs args) /// /// /// - /// /// /// - static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, int offsetY, bool[,] processed) + static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, bool[,] processed) { - // If the starting y position of this tile object is at (x, 0) and the y offset is negative, we'll be accessing tiles outside the rect - if (y + offsetY < 0) - { - TShock.Log.ConsoleDebug("Bouncer / SendTileRectHandler - rejected tile object because object dimensions fall outside the tile rect (negative y value)"); - return false; - } - - if (x + width > rectWidth || y + height + offsetY > rectLength) + if (x + width > rectWidth || y + height > rectLength) { // This is ugly, but we want to mark all these tiles as processed so that we're not hitting this check multiple times for one dodgy tile object for (int i = x; i < rectWidth; i++) { - for (int j = Math.Max(0, y + offsetY); j < rectLength; j++) // This is also ugly. Using Math.Max to make sure y + offsetY >= 0 + for (int j = y; j < rectLength; j++) { processed[i, j] = true; } From cf9240d56dd37a3b9d667003c595e31dfddab67b Mon Sep 17 00:00:00 2001 From: punchready Date: Wed, 27 Jul 2022 11:35:21 +0200 Subject: [PATCH 6/8] Support grass mowing --- TShockAPI/Handlers/SendTileRectHandler.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 7d073266c..455eaf647 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -331,6 +331,13 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect UpdateServerTileState(tile, newTile, TileDataType.Tile); } + if (rectWidth == 1 && rectLength == 1 && + (tile.type == TileID.Grass && newTile.Type == TileID.GolfGrass || + tile.type == TileID.HallowedGrass && newTile.Type == TileID.GolfGrassHallowed)) + { + UpdateServerTileState(tile, newTile, TileDataType.Tile); + } + if (rectWidth == 1 && rectLength == 1) // Conversion only sends a 1x1 rect { ProcessConversionSpreads(tile, newTile); From 9358f11e518a9d845aa6daada65308ea787bbc9c Mon Sep 17 00:00:00 2001 From: punchready Date: Sun, 14 Aug 2022 01:22:22 +0200 Subject: [PATCH 7/8] Improve comments --- TShockAPI/Bouncer.cs | 4 +- TShockAPI/Handlers/SendTileRectHandler.cs | 45 +++++++++++++++++------ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 736fc6d79..0f578998a 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -401,8 +401,8 @@ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args) return; } - // i do not understand the ice tile check enough to be able to modify it, however i do know that it can be used to completely bypass region protection - // this check ensures that build permission is always checked no matter what + // I do not understand the ice tile check enough to be able to modify it, however I do know that it can be used to completely bypass region protection + // This check ensures that build permission is always checked no matter what if (!args.Player.HasBuildPermission(tileX, tileY)) { TShock.Log.ConsoleDebug("Bouncer / OnTileEdit rejected from build from {0} {1} {2}", args.Player.Name, action, editData); diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs index 455eaf647..dbe80ec63 100644 --- a/TShockAPI/Handlers/SendTileRectHandler.cs +++ b/TShockAPI/Handlers/SendTileRectHandler.cs @@ -19,7 +19,10 @@ namespace TShockAPI.Handlers /// public class SendTileRectHandler : IPacketHandler { - private static readonly Dictionary> PlantToGrassMap = new Dictionary> + /// + /// Maps plant tile types to their valid grass ground tiles when using flower boots + /// + private static readonly Dictionary> FlowerBootPlantToGrassMap = new Dictionary> { { TileID.Plants, new HashSet() { @@ -39,19 +42,27 @@ public class SendTileRectHandler : IPacketHandler> PlantToStyleMap = new Dictionary>() + /// + /// Maps plant tile types to a list of valid styles, which are used to determine the FrameX value of the plant tile + /// See `Player.DoBootsEffect_PlaceFlowersOnTile` + /// + private static readonly Dictionary> FlowerBootPlantToStyleMap = new Dictionary>() { { TileID.Plants, new HashSet() { + // The upper line is from a `NextFromList` call + // The lower line is from an additional switch which will add the listed options by adding a random value to a select set of styles 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 27, 30, 33, 36, 39, 42, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, } }, { TileID.HallowedPlants, new HashSet() { + // 5 is intentionally missing here because it is being skipped by vanilla 4, 6, } }, { TileID.HallowedPlants2, new HashSet() { + // 5 is intentionally missing here because it is being skipped by vanilla 2, 3, 4, 6, 7, } }, { TileID.JunglePlants2, new HashSet() @@ -199,7 +210,7 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle case TileID.MinecartTrack: case TileID.ChristmasTree: { - // allowed changes + // Allowed changes } break; default: @@ -222,7 +233,7 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle case TileID.TeleportationPylon: case TileID.TargetDummy: { - // allowed placements + // Allowed placements } break; default: @@ -263,7 +274,7 @@ internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandle } } } - + /// /// Processes a tile object consisting of multiple tiles from the tile rect packet /// @@ -321,24 +332,30 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect ITile tile = Main.tile[realX, realY]; + // Triggering a single land mine tile if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.LandMine && !newTile.Active) { UpdateServerTileState(tile, newTile, TileDataType.Tile); } + // Hammering a single junction box if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.WirePipe) { UpdateServerTileState(tile, newTile, TileDataType.Tile); } + // Mowing a single grass tile: Grass -> GolfGrass OR HallowedGrass -> GolfGrassHallowed if (rectWidth == 1 && rectLength == 1 && - (tile.type == TileID.Grass && newTile.Type == TileID.GolfGrass || - tile.type == TileID.HallowedGrass && newTile.Type == TileID.GolfGrassHallowed)) + ( + tile.type == TileID.Grass && newTile.Type == TileID.GolfGrass || + tile.type == TileID.HallowedGrass && newTile.Type == TileID.GolfGrassHallowed + )) { UpdateServerTileState(tile, newTile, TileDataType.Tile); } - if (rectWidth == 1 && rectLength == 1) // Conversion only sends a 1x1 rect + // Conversion: only sends a 1x1 rect + if (rectWidth == 1 && rectLength == 1) { ProcessConversionSpreads(tile, newTile); } @@ -355,11 +372,15 @@ internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rect internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile) { ITile tile = Main.tile[realX, realY]; - // Ensure that the placed plant is valid for the grass below, that the target tile is empty, and that the placed plant has valid framing + // Ensure that: + // - the placed plant is valid for the grass below + // - the target tile is empty + // - and the placed plant has valid framing (style * 18 = FrameX) if ( - PlantToGrassMap.TryGetValue(newTile.Type, out HashSet grassTiles) && - !tile.active() && grassTiles.Contains(Main.tile[realX, realY + 1].type) && - PlantToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18)) + FlowerBootPlantToGrassMap.TryGetValue(newTile.Type, out HashSet grassTiles) && + !tile.active() && + grassTiles.Contains(Main.tile[realX, realY + 1].type) && + FlowerBootPlantToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18)) ) { UpdateServerTileState(tile, newTile, TileDataType.Tile); From 0ce7c94f1bf794bff1e0d0f23ae3ea243216ad02 Mon Sep 17 00:00:00 2001 From: punchready Date: Sun, 14 Aug 2022 01:22:29 +0200 Subject: [PATCH 8/8] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f03c092d4..1bc0b9cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin * Only allow using Teleportation Potions, Magic Conch, and Demon Conch whilst holding them. (@drunderscore) * Updated server startup language to be more clear when encountering a fatal startup error. Now, the server gives more context as to what happened so that there's a better chance of people being able to help themselves. (@hakusaro) * Added `-worldevil ` command line argument (@NotGeri) +* Fixed an exploit in which the Ice Block deletion allowance from the Ice Rod bypassed region protection, allowing for deleting all tiles in a protected region and/or replacing them with Ice Blocks. (@punchready) +* Changed SendTileRect handling from a denylist to an allowlist with stricter checks. This prevents essentially all exploits involving this packet. Most notably this stops people from placing arbitrary tiles with arbitrary framing values, which are the root of most exploits. (@punchready) +* Removed the config options `TileRectangleSizeThreshold` and `KickOnTileRectangleSizeThresholdBroken` because they are made obsolete by the new system, which will only allow valid rectangle sizes (at a maximum of only 4 by 4 tiles in 1.4.3.6). (@punchready) ## TShock 4.5.17 * Fixed duplicate characters (twins) after repeatedly logging in as the same character due to connection not being immediately closed during `NetHooks_NameCollision`. (@gohjoseph)