Bound replicated vector size before resize() - #2893
Conversation
std::vector<T> replication (both the generic sp::io operator>> and BASIC_REPLICATION_VECTOR) resizes to a uint32_t/size_t read directly off the network before reading a single element. A corrupt or hostile packet can announce a size near UINT32_MAX, forcing a multi-gigabyte allocation and crashing/DoS'ing whoever processes the packet. Clamp to a fixed upper bound (1,000,000 elements) and log a warning instead of trusting the announced size unconditionally.
|
This smells AI assisted/generated. I don't mind people using AI, but I do ask that if they did that they be up-front about it. |
|
Hello, I'm Eloy. Yes, that was AI generated, as a little project to learn how to use it in our free time we're doing https://github.com/VaroTv7/espaciokooplagunak. And a very big part of it basically is based on mixing Empty Epsilon and FoundryVTT, as we wanted to make a game that allows us to roleplay DnD while going on a cool spaceship. Anyway, as we were advancing there were some small fixes the AI/QA tools found (stuff like this one, or the CSS one, or some dependencies), so we thought it wouldn't hurt to open a few PRs upstream. But of course it's up to you guys if you want to accept them or not, if it's okay we can keep sending those upstream, and if not we'll just tell the AI to not do that. Best regards! |
|
AI-assisted review from the downstream Espaciokoop Lagunak maintainers, on The bound is a useful direction, but clamping does not fully close the DoS:
I would fail closed rather than clamp: detect the oversized declaration before mutating the destination and make the caller discard/reject the current replication payload (or add an error state/result to A focused regression should feed only an oversized size prefix and assert that the destination remains unchanged, no cap-sized vector is allocated, and the receive path reports/rejects the malformed payload. A second case with a truncated ordinary vector would cover the current silent-underflow behavior. The existing explicit mapping of both affected resize sites is good; this is about rejection semantics, not the original finding or attribution. |
|
Note that I'm fine with people using AI tooling (especially like this, where you just use it as an analyzer, and not to generate large blobs of code). I just like people to be up-front about it. I do fail to see the importance. As it's just the server that can DoS clients, as vector replication is only done in that direction, I'm not sure how realistic this problem really is. But a little protection in case of version mismatch is miss-interpreting packet data does not hurt. |
Problem
std::vector<T>replication reads a size prefix straight off the network and callsresize()with it before reading a single element — both the genericsp::io::operator>>forstd::vector<T>insrc/multiplayer/basic.hand theBASIC_REPLICATION_VECTORmacro'sReceivecase.A corrupt packet, or a hostile client inside a multiplayer session, can announce a size near
UINT32_MAX, forcing a multi-gigabyte allocation before any bounds/data-availability check happens — a memory-exhaustion DoS / crash for whoever processes the packet (client or server).For contrast, the per-element index in
BASIC_REPLICATION_VECTOR's receive loop is already validated against the vector's current size before writing — this is specifically about the unboundedresize()call itself.Fix
Clamp the announced size to a fixed upper bound (
max_replicated_vector_size = 1'000'000) before resizing, logging a warning when a packet exceeds it, in both places that resize from a wire-provided size.Testing
Built with
WARNING_IS_ERROR=1against a sibling SeriousProton checkout — full clean build succeeds (539/539 targets), no warnings introduced.I did not add an automated regression test for this — I'm not familiar enough with this codebase's network/replication test setup to know where a targeted test would fit; happy to add one if pointed at the right harness.