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

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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

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.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index