Add a test for sliceable cargo bounty exploits (#28357)

This commit is contained in:
Tayrtahn
2024-06-03 12:24:32 -04:00
committed by GitHub
parent 619d82ed42
commit b5e8a69622
3 changed files with 101 additions and 1 deletions

View File

@@ -3,8 +3,13 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Cargo.Components; using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems; using Content.Server.Cargo.Systems;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Cargo.Prototypes; using Content.Shared.Cargo.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -149,6 +154,80 @@ public sealed class CargoTest
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }
/// <summary>
/// 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.
/// </summary>
[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<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
var componentFactory = server.ResolveDependency<IComponentFactory>();
var whitelist = entManager.System<EntityWhitelistSystem>();
var cargo = entManager.System<CargoSystem>();
var sliceableSys = entManager.System<SliceableFoodSystem>();
var bounties = protoManager.EnumeratePrototypes<CargoBountyPrototype>().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<EntityPrototype>()
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => p.TryGetComponent<SliceableFoodComponent>(out _, componentFactory))
.Select(p => p.ID)
.ToList();
foreach (var proto in sliceableEntityProtos)
{
var ent = entManager.SpawnEntity(proto, coord);
var sliceable = entManager.GetComponent<SliceableFoodComponent>(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] [TestPrototypes]
private const string StackProto = @" private const string StackProto = @"

View File

@@ -300,6 +300,21 @@ public sealed partial class CargoSystem
return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities); return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities);
} }
/// <summary>
/// Determines whether the <paramref name="entity"/> meets the criteria for the bounty <paramref name="entry"/>.
/// </summary>
/// <returns>true if <paramref name="entity"/> is a valid item for the bounty entry, otherwise false</returns>
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<EntityUid> entities, IEnumerable<CargoBountyItemEntry> entries, out HashSet<EntityUid> bountyEntities) public bool IsBountyComplete(HashSet<EntityUid> entities, IEnumerable<CargoBountyItemEntry> entries, out HashSet<EntityUid> bountyEntities)
{ {
bountyEntities = new(); bountyEntities = new();
@@ -313,7 +328,7 @@ public sealed partial class CargoSystem
var temp = new HashSet<EntityUid>(); var temp = new HashSet<EntityUid>();
foreach (var entity in entities) foreach (var entity in entities)
{ {
if (!_whitelistSys.IsValid(entry.Whitelist, entity) || (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))) if (!IsValidBountyEntry(entity, entry))
continue; continue;
count += _stackQuery.CompOrNull(entity)?.Count ?? 1; count += _stackQuery.CompOrNull(entity)?.Count ?? 1;

View File

@@ -42,6 +42,9 @@
whitelist: whitelist:
tags: tags:
- Bread - Bread
blacklist:
tags:
- Slice
- type: cargoBounty - type: cargoBounty
id: BountyCarrot id: BountyCarrot
@@ -533,6 +536,9 @@
whitelist: whitelist:
tags: tags:
- Meat - Meat
blacklist:
components:
- SliceableFood
- type: cargoBounty - type: cargoBounty
id: BountyFruit id: BountyFruit