Skip to content

Fix use-after-free in TCrossConnectionBase.Close - #197

Merged
winddriver merged 1 commit into
winddriver:masterfrom
Vizit0r:fix/connection-close-self-ref
Aug 5, 2026
Merged

Fix use-after-free in TCrossConnectionBase.Close#197
winddriver merged 1 commit into
winddriver:masterfrom
Vizit0r:fix/connection-close-self-ref

Conversation

@Vizit0r

@Vizit0r Vizit0r commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
procedure TCrossConnectionBase.Close;
begin
  if (_SetConnectStatus(csClosed) = csClosed) then Exit;

  if (FSocket <> INVALID_SOCKET) then
  begin
    FOwner.TriggerDisconnected(Self);   // <-- may destroy Self
    InternalClose;                      // <-- on freed memory
    FSocket := INVALID_SOCKET;          // <-- on freed memory
  end;
end;

Self is an object reference being passed where an ICrossConnection is expected, so the compiler materialises a temporary interface reference and releases it at the end of that statement. Inside the call, TriggerDisconnected does

FConnections.Remove(AConnection.UID);
...
LogicDisconnected(AConnection);

Either the list removal or the user callback can drop the last remaining reference to the connection. When that happens, the temporary is the only thing keeping the object alive, and it is gone by the time InternalClose and the FSocket reset run — both then touch freed memory.

Most call sites happen to be safe because they hold their own LConnection: ICrossConnection local, so this only bites on the paths where the caller's reference has already been dropped.

Fix

Hold an ICrossConnection for the whole method. Two lines, no behaviour change on the paths that were already safe.

How it showed up

EUseAfterFreeError in production (FastMM full-debug), reached through TIocpCrossSocket.Send -> AConnection.Close on an already-dead socket.

Checked

Compiles clean on Win64 (Delphi 37.0) — no new warnings or hints.

TCrossConnectionBase.Close passes Self to FOwner.TriggerDisconnected as an
ICrossConnection. The compiler builds a temporary interface reference that is
released at the end of that statement, while TriggerDisconnected removes the
connection from FConnections and then runs LogicDisconnected -- either of
which can drop the last remaining reference. When it does, the object is gone
before InternalClose runs and before FSocket is reset, so both touch freed
memory.

Keep an ICrossConnection reference alive for the whole method.

Seen in production as EUseAfterFreeError reached through
TIocpCrossSocket.Send -> AConnection.Close on an already dead socket.
@winddriver
winddriver merged commit f40417f into winddriver:master Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants