From: Tayrtahn Date: Mon, 3 Jun 2024 16:24:32 +0000 (-0400) Subject: Add a test for sliceable cargo bounty exploits (#28357) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=b5e8a696225a3c790939b67a832067caa1354c67;p=space-station-14.git Add a test for sliceable cargo bounty exploits (#28357) --- diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs index 09f179cf4f..8e1d536054 100644 --- a/Content.IntegrationTests/Tests/CargoTest.cs +++ b/Content.IntegrationTests/Tests/CargoTest.cs @@ -3,8 +3,13 @@ using System.Linq; using System.Numerics; using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; +using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.EntitySystems; using Content.Shared.Cargo.Prototypes; +using Content.Shared.IdentityManagement; using Content.Shared.Stacks; +using Content.Shared.Tag; +using Content.Shared.Whitelist; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -149,6 +154,80 @@ public sealed class CargoTest await pair.CleanReturnAsync(); } + /// + /// Tests to see if any items that are valid for cargo bounties can be sliced into items that + /// are also valid for the same bounty entry. + /// + [Test] + public async Task NoSliceableBountyArbitrageTest() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var testMap = await pair.CreateTestMap(); + + var entManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + var protoManager = server.ResolveDependency(); + var componentFactory = server.ResolveDependency(); + var whitelist = entManager.System(); + var cargo = entManager.System(); + var sliceableSys = entManager.System(); + + var bounties = protoManager.EnumeratePrototypes().ToList(); + + await server.WaitAssertion(() => + { + var mapId = testMap.MapId; + var grid = mapManager.CreateGridEntity(mapId); + var coord = new EntityCoordinates(grid.Owner, 0, 0); + + var sliceableEntityProtos = protoManager.EnumeratePrototypes() + .Where(p => !p.Abstract) + .Where(p => !pair.IsTestPrototype(p)) + .Where(p => p.TryGetComponent(out _, componentFactory)) + .Select(p => p.ID) + .ToList(); + + foreach (var proto in sliceableEntityProtos) + { + var ent = entManager.SpawnEntity(proto, coord); + var sliceable = entManager.GetComponent(ent); + + // Check each bounty + foreach (var bounty in bounties) + { + // Check each entry in the bounty + foreach (var entry in bounty.Entries) + { + // See if the entity counts as part of this bounty entry + if (!cargo.IsValidBountyEntry(ent, entry)) + continue; + + // Spawn a slice + var slice = entManager.SpawnEntity(sliceable.Slice, coord); + + // See if the slice also counts for this bounty entry + if (!cargo.IsValidBountyEntry(slice, entry)) + { + entManager.DeleteEntity(slice); + continue; + } + + entManager.DeleteEntity(slice); + + // If for some reason it can only make one slice, that's okay, I guess + Assert.That(sliceable.TotalCount, Is.EqualTo(1), $"{proto} counts as part of cargo bounty {bounty.ID} and slices into {sliceable.TotalCount} slices which count for the same bounty!"); + } + } + + entManager.DeleteEntity(ent); + } + mapManager.DeleteMap(mapId); + }); + + await pair.CleanReturnAsync(); + } [TestPrototypes] private const string StackProto = @" diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index 0fcfd160bb..554b349b9b 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -300,6 +300,21 @@ public sealed partial class CargoSystem return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities); } + /// + /// Determines whether the meets the criteria for the bounty . + /// + /// true if is a valid item for the bounty entry, otherwise false + public bool IsValidBountyEntry(EntityUid entity, CargoBountyItemEntry entry) + { + if (!_whitelistSys.IsValid(entry.Whitelist, entity)) + return false; + + if (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity)) + return false; + + return true; + } + public bool IsBountyComplete(HashSet entities, IEnumerable entries, out HashSet bountyEntities) { bountyEntities = new(); @@ -313,7 +328,7 @@ public sealed partial class CargoSystem var temp = new HashSet(); foreach (var entity in entities) { - if (!_whitelistSys.IsValid(entry.Whitelist, entity) || (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))) + if (!IsValidBountyEntry(entity, entry)) continue; count += _stackQuery.CompOrNull(entity)?.Count ?? 1; diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml index bedfe44287..08bb2a1422 100644 --- a/Resources/Prototypes/Catalog/Bounties/bounties.yml +++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml @@ -42,6 +42,9 @@ whitelist: tags: - Bread + blacklist: + tags: + - Slice - type: cargoBounty id: BountyCarrot @@ -533,6 +536,9 @@ whitelist: tags: - Meat + blacklist: + components: + - SliceableFood - type: cargoBounty id: BountyFruit