Skip to content

fix(netpacket): Fix undefined behavior with NetPacket buffer writes#2304

Open
xezon wants to merge 1 commit intoTheSuperHackers:mainfrom
xezon:xezon/fix-netpacket-unaligned-write
Open

fix(netpacket): Fix undefined behavior with NetPacket buffer writes#2304
xezon wants to merge 1 commit intoTheSuperHackers:mainfrom
xezon:xezon/fix-netpacket-unaligned-write

Conversation

@xezon
Copy link

@xezon xezon commented Feb 14, 2026

This change fixes the undefined behavior with NetPacket buffer writes in NetPacket::FillBuffer functions.

TODO

  • Test in Network match

@xezon xezon added Minor Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers Gen Relates to Generals ZH Relates to Zero Hour Fix Is fixing something, but is not user facing Stability Concerns stability of the runtime labels Feb 14, 2026
@greptile-apps
Copy link

greptile-apps bot commented Feb 14, 2026

Greptile Overview

Greptile Summary

This PR fixes undefined behavior in all NetPacket::FillBufferWith* serialization functions by replacing placement-new construction directly into raw UnsignedByte* buffers with stack-local construction followed by memcpy.

  • Root cause: The NetPacket* structs (defined in NetPacketStructs.h) have non-trivial constructors that initialize field type marker bytes. The target buffers are raw UnsignedByte arrays (NEW UnsignedByte[bufferSize] or m_packet[MAX_PACKET_SIZE]) with no alignment guarantees. Using placement-new on potentially misaligned buffers and then accessing through both raw byte and struct pointers violates strict aliasing rules — all of which constitute undefined behavior.
  • Fix: Each of the 20 FillBufferWith* functions now constructs the packet struct as a local stack variable (guaranteeing proper alignment and correct constructor execution), then copies the initialized struct into the buffer using memcpy — the canonical safe serialization pattern in C++.
  • Variable data portions (game messages, chat text, file data) that follow the fixed-size struct headers were already using memcpy and remain unchanged, with correct offset calculations referencing sizeof(PacketType).
  • The change is purely mechanical and consistent across all affected functions, with no behavioral differences in the serialized output.

Confidence Score: 5/5

  • This PR is safe to merge — it replaces undefined behavior with well-defined semantics while producing identical serialized output.
  • The change is mechanical and consistent across all 20 FillBufferWith* functions. Each follows the same pattern: construct on stack, populate fields, memcpy to buffer. The struct definitions are pragma-packed to 1-byte alignment ensuring sizeof matches the wire format. Variable data portions already used memcpy and are unchanged. No new logic, no behavioral changes, no edge cases introduced.
  • No files require special attention.

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameNetwork/NetPacket.cpp Replaces placement-new into raw byte buffers with stack construction + memcpy across all 20 FillBufferWith* functions, eliminating alignment and strict aliasing undefined behavior. All changes are mechanical and consistent.

Flowchart

flowchart TD
    A["FillBufferWithCommand(buffer, ref)"] --> B{Switch on\nNetCommandType}
    B --> C["FillBufferWithGameCommand"]
    B --> D["FillBufferWithAckCommand"]
    B --> E["FillBufferWithFrameCommand"]
    B --> F["...18 other FillBufferWith* functions"]

    subgraph old["Old Pattern (Undefined Behavior)"]
        G["placement-new: NetPacketXxx* p = new (buffer) NetPacketXxx"] --> H["Write fields via p->field = value"]
    end

    subgraph new["New Pattern (Well-Defined)"]
        I["Stack construct: NetPacketXxx packet"] --> J["Write fields via packet.field = value"]
        J --> K["memcpy(buffer, &packet, sizeof(packet))"]
    end

    C --> new
    D --> new
    E --> new
    F --> new

    K --> L["Append variable data via memcpy at offset sizeof(NetPacketXxx)"]
Loading

Last reviewed commit: 55e69ea

packet->relay.relay = msg->getRelay();
packet->playerId.playerId = cmdMsg->getPlayerID();
packet->commandId.commandId = cmdMsg->getID();
NetPacketGameCommand packet;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering how frequently these are used, maybe we make these static so we aren't always initialising the structs?
The variable parts are always updated with each use so it shouldn't be an issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear to me if that would give anything for performance. Making it static means it goes into the DATA segment. So CPU would need to write and read from DATA segment to heap buffer, which of course is fast, but maybe write and read from code segment to stack to heap buffer is just fine? Code instructions and Stack are hot. DATA segment maybe not as hot?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fair, there's probably nothing to it in the grand scheme of things since we don't need to persist any values between calls.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can revisit this when it indeed shows up in profiler. I doubt it will.

memcpy(buffer + offset, unitext.str(), packet->textLength * sizeof(UnsignedShort));
offset += packet->textLength * sizeof(UnsignedShort);
memcpy(buffer + offset, unitext.str(), packet.textLength * sizeof(UnsignedShort));
offset += packet.textLength * sizeof(UnsignedShort);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a local variable, this offset adjustment seems redundant here.
I think fill buffer with chat command does the same thing.

seems like this was a copypasta in this situation from the other function

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the file transfer command does the same thing

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I already have a follow up refactor for afterwards that cleans this up. Outside the scope of this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Fix Is fixing something, but is not user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers Stability Concerns stability of the runtime ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants