XMPP: frame-transport send/close zonder exception handling — socket-exception kan app crashen of HttpClient lekken #1430

Closed
opened 2026-08-09 18:35:16 +00:00 by brenno · 0 comments
Owner

Bevinding

lib/xmpp/xmpp_frame_transport_io.dart_WebSocketFrameTransport heeft twee plekken waar exceptions niet worden opgevangen:

1. send() gooit als socket gesloten is (regel 117–119)

void send(String frame) {
  if (!_closed) _socket.add(frame);
}

Als de server de WebSocket heeft gesloten maar _closed is nog niet gezet (de close-event is async en kan nog in de event-queue staan), gooit _socket.add(frame) een StateError of WebSocketException. Deze exception propagatet naar de aanroeper — XmppSession.sendStanza (regel 592) of XmppSession._send (regel 616) — die hem niet opvangt. De exception belandt in de event-loop als een unhandled exception, wat de app kan crashen of een error-dialog toont.

Dit is het meest waarschijnlijk tijdens een reconnect-scenario: de oude transport is gesloten door de server, maar de sessie probeert nog te verzenden vóórdat _onStreamDropped heeft gevuuurd.

2. close() lekt HttpClient als socket.close() gooit (regel 122–127)

Future<void> close() async {
  if (_closed) return;
  _closed = true;
  await _socket.close();
  _client.close(force: true);
}

Als _socket.close() een exception gooit (bijv. de socket is al gesloten door de server), wordt _client.close(force: true) nooit aangeroepen. De HttpClient lekt — zijn eventueel openstaande connecties en timers worden niet opgeruimd.

Trust boundary

Netwerk → Applicatie (socket-lifecycle). Geen externe aanvaller — een gewone netwerkdrop volstaat.

Impact

  • send(): een unhandled exception in de event-loop kan de app crashen of een error-dialog tonen. Het meest waarschijnlijk tijdens een netwerkdrop.
  • close(): een lekkende HttpClient houdt connecties en timers open, wat geheugen en file-descriptors lekt. Bij herhaalde connect/disconnect-cycli kan dit oplopen.

Oplossingsrichting

  1. send(): vang de exception op en log deze, in plaats van te propagaten:
void send(String frame) {
  if (_closed) return;
  try {
    _socket.add(frame);
  } catch (e) {
    logWarning('xmpp.transport.send', e);
    // De socket is dood — markeer als gesloten zodat latere sends no-ops zijn.
    _closed = true;
  }
}
  1. close(): gebruik een try-finally om _client.close(force: true) altijd uit te voeren:
Future<void> close() async {
  if (_closed) return;
  _closed = true;
  try {
    await _socket.close();
  } catch (e) {
    logWarning('xmpp.transport.close', e);
  } finally {
    _client.close(force: true);
  }
}

Locatie

  • lib/xmpp/xmpp_frame_transport_io.dart regels 117–119 (send), 122–127 (close)

Severity

MEDIUM — send() kan een crash veroorzaken tijdens een netwerkdrop; close() lekt een HttpClient.

## Bevinding `lib/xmpp/xmpp_frame_transport_io.dart` — `_WebSocketFrameTransport` heeft twee plekken waar exceptions niet worden opgevangen: ### 1. `send()` gooit als socket gesloten is (regel 117–119) ```dart void send(String frame) { if (!_closed) _socket.add(frame); } ``` Als de server de WebSocket heeft gesloten maar `_closed` is nog niet gezet (de close-event is async en kan nog in de event-queue staan), gooit `_socket.add(frame)` een `StateError` of `WebSocketException`. Deze exception propagatet naar de aanroeper — `XmppSession.sendStanza` (regel 592) of `XmppSession._send` (regel 616) — die hem niet opvangt. De exception belandt in de event-loop als een unhandled exception, wat de app kan crashen of een error-dialog toont. Dit is het meest waarschijnlijk tijdens een reconnect-scenario: de oude transport is gesloten door de server, maar de sessie probeert nog te verzenden vóórdat `_onStreamDropped` heeft gevuuurd. ### 2. `close()` lekt HttpClient als socket.close() gooit (regel 122–127) ```dart Future<void> close() async { if (_closed) return; _closed = true; await _socket.close(); _client.close(force: true); } ``` Als `_socket.close()` een exception gooit (bijv. de socket is al gesloten door de server), wordt `_client.close(force: true)` nooit aangeroepen. De `HttpClient` lekt — zijn eventueel openstaande connecties en timers worden niet opgeruimd. ### Trust boundary Netwerk → Applicatie (socket-lifecycle). Geen externe aanvaller — een gewone netwerkdrop volstaat. ### Impact - **send()**: een unhandled exception in de event-loop kan de app crashen of een error-dialog tonen. Het meest waarschijnlijk tijdens een netwerkdrop. - **close()**: een lekkende HttpClient houdt connecties en timers open, wat geheugen en file-descriptors lekt. Bij herhaalde connect/disconnect-cycli kan dit oplopen. ### Oplossingsrichting 1. **`send()`:** vang de exception op en log deze, in plaats van te propagaten: ```dart void send(String frame) { if (_closed) return; try { _socket.add(frame); } catch (e) { logWarning('xmpp.transport.send', e); // De socket is dood — markeer als gesloten zodat latere sends no-ops zijn. _closed = true; } } ``` 2. **`close()`:** gebruik een try-finally om `_client.close(force: true)` altijd uit te voeren: ```dart Future<void> close() async { if (_closed) return; _closed = true; try { await _socket.close(); } catch (e) { logWarning('xmpp.transport.close', e); } finally { _client.close(force: true); } } ``` ### Locatie - `lib/xmpp/xmpp_frame_transport_io.dart` regels 117–119 (`send`), 122–127 (`close`) ### Severity MEDIUM — send() kan een crash veroorzaken tijdens een netwerkdrop; close() lekt een HttpClient.
brenno 2026-08-09 20:56:39 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#1430
No description provided.