CNA-BUG-171: LocalNetworkGamer::ReceiveData(PacketReader&, NetworkGamer*&) does not resize the reader, so a reused reader keeps the previous packet's Length and trailing bytes
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
The overload writes the packet at position 0 of the reader's MemoryStream without truncating it, so after a longer packet a reused PacketReader reports the old Length and exposes stale bytes past the new packet, where XNA resizes the reader to the packet.
- Identifier
CNA-BUG-171- Category
- Bug
- Subsystem
- Networking & gamer services
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Microsoft::Xna::Framework::Net::LocalNetworkGamer::ReceiveData(PacketReader&, NetworkGamer*&); PacketReader::getLengthProperty() and getPositionProperty()
Expected behaviour
XNA 4.0's ReceiveData(PacketReader, out NetworkGamer) calls data.Resize(size of the next packet) before copying (decompiled LocalNetworkGamer; PacketReader.Resize sets the stream length), so Length always equals the received packet's size and one reader can be reused for every packet, as XNA samples do.
Actual behaviour at TARGET
LocalNetworkGamer.cpp sets the reader's position to 0, writes the packet bytes through the base MemoryStream and sets the position to 0 again; it never sets the length. The Sharp Runtime MemoryStream only grows on Write, and PacketReader::getLengthProperty() reads the stream length, so after a 20-byte packet followed by an 8-byte one the reader reports Length 20, and bytes 8 to 19 are the tail of the previous packet. Code that reads until Position reaches Length, a common XNA loop, reads stale data; code that reads a fixed layout, as CNA's own networking demos do, is unaffected. The overload also returns 0 whatever it received, an FNA quirk the header documents as preserved, which is not part of this entry.
Source locations
modules/net/src/Xna/LocalNetworkGamer.cpp— LocalNetworkGamer::ReceiveData PacketReader overload: position reset and write, no length changemodules/net/src/Xna/PacketReader.cpp— getLengthProperty returns the base stream lengthmodules/net/include/Microsoft/Xna/Framework/Net/LocalNetworkGamer.hpp— documents the preserved zero return valuemodules/net/tests/Microsoft/Xna/Framework/Net/LocalNetworkGamerTests.cpp— ReceiveDataIntoPacketReaderReturnsZero covers only the empty queue
Evidence
Checked by reading at 009d40f5; not executed. MemoryStream::Write and getLengthProperty were read in a sibling Sharp Runtime checkout (next at 41b918c9, not pinned by TARGET): Write resizes its buffer only upward and the length is the buffer size. The XNA behaviour is from the decompiled LocalNetworkGamer and PacketReader. A chapter review of this area had recorded the concern as unclear because of the sibling dependency; reading the sibling settles it for that revision.
Independently observed as a separate finding (merged): The overload writes the packet at position 0 of the reader's MemoryStream and never sets its length; after a longer packet, a shorter one leaves the old Length and tail bytes in place.
Focused reproduction
// Illustrative; not compiled or run for this entry. SystemLink session in which the local
// gamer sends a 20-byte and then an 8-byte packet to itself (as LocalNetworkGamerTests'
// SystemLink fixture does), with Update() after each send.
PacketReader reader; // reused, as XNA samples do
NetworkGamer* sender = nullptr;
me->ReceiveData(reader, sender); // Length 20
me->ReceiveData(reader, sender); // Length still 20; bytes 8..19 are from the first packet
while (reader.getPositionProperty() < reader.getLengthProperty())
{
// reads the 8 new bytes, then 12 stale ones
}
Current tests
LocalNetworkGamerTests.cpp covers the byte-vector overloads (including a reused PacketWriter) and the empty-queue PacketReader case; ENetBackendTests.cpp and the demos read fixed layouts. No test receives two packets of different sizes into one reader.
Regression test
With the SystemLink fixture, send a long and then a short packet to the local gamer, receive both into one reader, and assert that after the second call Length equals the short packet's size and Position is 0; fixed by setting the stream length to the packet size before writing.
Blast radius
Games that reuse one PacketReader and read until Position reaches Length, or check Length to validate a packet: they read stale bytes after any shorter packet. Fixed-layout readers and the byte-vector overloads are unaffected.
Workaround
Construct a new PacketReader per packet, or use the byte-vector ReceiveData overload and its returned length.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Internals
- Network session internals: the pump
- Known issues
- Bug index