A pilot leaving mid-refresh no longer takes the room with them
From the playtest dump: an access violation reading address zero inside strncpy, two minutes into a session, dispatched from a window message - WM_TIMER, wParam 7. Seven is kLobbyTimerId. That timer runs every 750ms while hosting or seated, and its work is RefreshRoster -> RPL4Lobby_RosterLines -> CollectMembers, which reads each member's row out of the lobby. CollectMembers took Steam's answers straight as strings: eleven strncpy and atoi calls fed directly from GetLobbyMemberData with nothing between. Steam answers NULL - not "" - for a member it no longer knows, and the roster is polled on a timer, so somebody leaving between GetNumLobbyMembers and the read of their row is ordinary rather than exotic. strncpy then walks off address zero, which is exactly the faulting instruction. The file already knew this: LobbyText() has guarded the lobby-wide reads from the start, and all six other direct readers test for NULL. Only the member rows went unguarded. So they get the matching accessor, MemberText(), and CollectMembers goes through it - which covers the two atoi calls as well, since atoi(NULL) reads address zero just as happily. Not confirmed frame by frame: the dump is a tester's own build (D:\Games\RP412, stamped 13 Aug 16:33) and no matching pdb came with it, so the frames resolve only as offsets. What is symbol-independent is the message number and the timer id, read straight off the stack; from there this is the one path in that handler that does an unguarded strncpy on a pointer Steam is allowed to return NULL for. The call depth after inlining matches too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+33
-16
@@ -293,6 +293,26 @@ namespace
|
||||
return (text != NULL) ? text : "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// The same for one member's own row.
|
||||
//
|
||||
// Steam answers NULL - not "" - for a member it no longer knows,
|
||||
// and the roster is polled on a 750ms timer, so somebody leaving
|
||||
// between GetNumLobbyMembers and the read of their row is ordinary
|
||||
// rather than exotic. Every caller treats the answer as a string:
|
||||
// strncpy walks off address zero, and so does atoi.
|
||||
//-------------------------------------------------------------------
|
||||
const char *MemberText(CSteamID member, const char *key)
|
||||
{
|
||||
if (!gInLobby)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
const char *text =
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member, key);
|
||||
return (text != NULL) ? text : "";
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Launch: roster into lobby data, peers into the transport, and
|
||||
// the hosted-race path primed on the owner
|
||||
@@ -328,39 +348,36 @@ namespace
|
||||
memset(member, 0, sizeof(*member));
|
||||
member->id = SteamMatchmaking()->GetLobbyMemberByIndex(gLobby, i);
|
||||
strncpy(member->ip,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "ip"),
|
||||
MemberText(member->id, "ip"),
|
||||
sizeof(member->ip) - 1);
|
||||
member->consolePort = atoi(
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "cp"));
|
||||
member->gamePort = atoi(
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "gp"));
|
||||
member->consolePort = atoi(MemberText(member->id, "cp"));
|
||||
member->gamePort = atoi(MemberText(member->id, "gp"));
|
||||
strncpy(member->name,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "nm"),
|
||||
MemberText(member->id, "nm"),
|
||||
sizeof(member->name) - 1);
|
||||
strncpy(member->vehicle,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "vh"),
|
||||
MemberText(member->id, "vh"),
|
||||
sizeof(member->vehicle) - 1);
|
||||
strncpy(member->color,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "cl"),
|
||||
MemberText(member->id, "cl"),
|
||||
sizeof(member->color) - 1);
|
||||
strncpy(member->badge,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "bd"),
|
||||
MemberText(member->id, "bd"),
|
||||
sizeof(member->badge) - 1);
|
||||
strncpy(member->team,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "tm"),
|
||||
MemberText(member->id, "tm"),
|
||||
sizeof(member->team) - 1);
|
||||
strncpy(member->position,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "ps"),
|
||||
MemberText(member->id, "ps"),
|
||||
sizeof(member->position) - 1);
|
||||
strncpy(member->netRev,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, kNetRevKey),
|
||||
MemberText(member->id, kNetRevKey),
|
||||
sizeof(member->netRev) - 1);
|
||||
strncpy(member->build,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, kBuildKey),
|
||||
MemberText(member->id, kBuildKey),
|
||||
sizeof(member->build) - 1);
|
||||
member->camera = (atoi(
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, kCamKey)) != 0)
|
||||
? True : False;
|
||||
member->camera =
|
||||
(atoi(MemberText(member->id, kCamKey)) != 0) ? True : False;
|
||||
member->published =
|
||||
member->ip[0] != '\0' && member->consolePort > 0 && member->gamePort > 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user