Skip to content

GUACAMOLE-2293: Release active connection when guacd socket teardown throws on close#1223

Open
nabaco wants to merge 1 commit into
apache:mainfrom
nabaco:GUACAMOLE-2293-ghost-session-leak
Open

GUACAMOLE-2293: Release active connection when guacd socket teardown throws on close#1223
nabaco wants to merge 1 commit into
apache:mainfrom
nabaco:GUACAMOLE-2293-ghost-session-leak

Conversation

@nabaco

@nabaco nabaco commented Jul 1, 2026

Copy link
Copy Markdown

ManagedInetGuacamoleSocket / ManagedSSLGuacamoleSocket run their cleanup task (socketClosedTask, which removes the in-memory ActiveConnectionRecord and releases its concurrency seat) after super.close(), not in a finally. At disconnect the guacd socket is closed nearly simultaneously by the read thread and by @OnClose. When the two race, the JDK's NIO layer throws IOException on the second close() of the same fd (the close() syscall succeeds - it is a NIO double-close artifact). super.close() then throws, the cleanup task is skipped, and the active connection leaks in memory until guacamole-app restarts.

Run socketClosedTask in a finally so cleanup always happens, regardless of whether super.close() throws (ConnectionCleanupTask already guards itself to run once, so this is safe).

Also drop the tunnel.isOpen() guard in ActiveConnectionService.deleteObject() and always call tunnel.close(). Once the peer has vanished isOpen() returns false, which made "Kill session" a no-op and left admins unable to reap a leaked connection.

…throws on close.

ActiveConnectionService.deleteObject() only closed the tunnel when
tunnel.isOpen() was true. AbstractGuacamoleTunnel.isOpen() delegates to
the underlying socket, so once the remote peer (e.g. a WebSocket client
that disconnected abruptly behind a proxy/tunnel) is gone, isOpen()
returns false and the kill request becomes a no-op: the
ConnectionCleanupTask never runs, so the in-memory active connection
record and its concurrency seat are never released. The 'Kill session'
UI/REST action then appears to succeed while the ghost session persists.

More fundamentally, ManagedInetGuacamoleSocket.close() and
ManagedSSLGuacamoleSocket.close() ran the socket-closed cleanup task
after super.close() with no finally. When the underlying guacd socket is
being torn down concurrently (the read thread and @onclose both close it),
super.close() can throw (JDK's NIO layer surfaces a spurious IOException
on the second close) and the cleanup task is skipped, leaking the active
connection record even on a normal disconnect.

Always call tunnel.close() on delete (the cleanup task is idempotent),
and run the socket-closed cleanup task in a finally block so cleanup
happens even when closing an already-dead socket throws.
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.

1 participant