XMPP: stanzas verzonden tijdens reconnect gaan stil verloren — chat lokaal geëchood maar nooit afgeleverd #1417

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

Bevinding

lib/xmpp/xmpp_session.dartsendStanza (regels 590–593) is een no-op als de sessie niet live is:

void sendStanza(Stanza stanza) {
  if (_closed || !_live) return;
  transport.send(stanza.toXmlString());
}

Tijdens een reconnect is _live = false (regel 516 in _onStreamDropped). Alle stanzas die in die periode worden verzonden, worden stil gedropt — geen fout, geen buffering, geen retry.

Wat er gebeurt per kanaal

  • Chat (XmppChat.send): het bericht wordt lokaal geëchood (regel 134–141, vóór _channel.sendStanza) en de gebruiker ziet het direct in de UI. Maar sendStanza dropt het stil. De gebruiker denkt dat zijn bericht is verzonden; andere deelnemers zien het nooit. Er is geen indicator dat het bericht niet is afgeleverd.
  • Keyshare (XmppKeyExchange._sendKeyshare): een keyshare voor een newcomer die net is goedgekeurd, gaat verloren. De newcomer wordt nooit gesleuteld en kan niet meedoen. De autoriteit heeft geen doorzicht dat de keyshare niet aankwam.
  • Ops/locks (XmppTransport.sendOp/setLock): een op die tijdens de reconnect wordt verzonden, gaat verloren. De gap→resync-mechanisme vangt dit op de follower-kant (een gemiste op triggert een resync), maar op de autoriteit-kant is er geen follower die een gap detecteert — de autoriteit's eigen op is gewoon weg.
  • Presence (XmppPresenceBeacon.announce): de laatste presence wordt bij de volgende announce opnieuw verzonden (als de slide veranderd is), dus dit is grotendeels zelfherstellend.

Trust boundary

Interne logica — geen externe aanvaller. Een gewone netwerkdrop volstaat.

Impact

  • Chatberichten gaan verloren met een valse bevestiging (lokale echo) — de gebruiker denkt dat ze zijn afgeleverd.
  • Keyshares gaan verloren — newcomers worden niet gesleuteld.
  • Autoriteit's ops gaan verloren — de deck-state divergeert stil.

Oplossingsrichting

  1. Buffer outbound stanzas tijdens reconnect. Laat XmppSession een korte outbound-queue bijhouden (begrensd, bijv. 64 stanzas). sendStanza voegt toe aan de queue als !_live; na een geslaagde reconnect spoelt _reconnect de queue naar de nieuwe transport. Bij een fail-closed (max pogingen) wordt de queue gewist.
  2. Voor chat specifiek: markeer lokaal geëchoode berichten als "verzonden" pas nadat sendStanza bevestigt dat de stanza op de wire is gezet. Een bericht dat in de buffer staat, toont een "verzenden…"-indicator. Dit vereist dat sendStanza een boolean retourneert of een callback krijgt.
  3. Voor keyshares specifiek: de autoriteit's ensureKeyed is idempotent en wordt elke syncNow-ronde aangeroepen — een verloren keyshare wordt op de volgende ronde opnieuw verzonden (als de newcomer nog in de approval-set staat). Dit is grotendeels zelfherstellend, maar de ronde-duur (1 seconde) betekent maximaal 1 seconde vertraging.
  4. Voor autoriteit's ops: overweeg een bevestigings-mechanisme — de autoriteit hoort zijn eigen op terug via de MUC-reflectie; als die niet komt, is de op waarschijnlijk verloren.

Locatie

  • lib/xmpp/xmpp_session.dart regels 590–593 (sendStanza), 515–526 (_onStreamDropped — zet _live = false)
  • lib/xmpp/xmpp_chat.dart regels 124–157 (send — lokale echo vóór sendStanza)
  • lib/xmpp/xmpp_key_exchange.dart regels 170–185 (_sendKeyshare)
  • lib/xmpp/xmpp_transport.dart regels 195–209 (sendOp)

Severity

HIGH — chatberichten met valse bevestiging en verloren keyshares zijn het ernstigst.

## Bevinding `lib/xmpp/xmpp_session.dart` — `sendStanza` (regels 590–593) is een no-op als de sessie niet live is: ```dart void sendStanza(Stanza stanza) { if (_closed || !_live) return; transport.send(stanza.toXmlString()); } ``` Tijdens een reconnect is `_live = false` (regel 516 in `_onStreamDropped`). Alle stanzas die in die periode worden verzonden, worden **stil gedropt** — geen fout, geen buffering, geen retry. ### Wat er gebeurt per kanaal - **Chat (`XmppChat.send`):** het bericht wordt **lokaal geëchood** (regel 134–141, vóór `_channel.sendStanza`) en de gebruiker ziet het direct in de UI. Maar `sendStanza` dropt het stil. De gebruiker denkt dat zijn bericht is verzonden; andere deelnemers zien het nooit. Er is geen indicator dat het bericht niet is afgeleverd. - **Keyshare (`XmppKeyExchange._sendKeyshare`):** een keyshare voor een newcomer die net is goedgekeurd, gaat verloren. De newcomer wordt nooit gesleuteld en kan niet meedoen. De autoriteit heeft geen doorzicht dat de keyshare niet aankwam. - **Ops/locks (`XmppTransport.sendOp`/`setLock`):** een op die tijdens de reconnect wordt verzonden, gaat verloren. De gap→resync-mechanisme vangt dit op de *follower*-kant (een gemiste op triggert een resync), maar op de *autoriteit*-kant is er geen follower die een gap detecteert — de autoriteit's eigen op is gewoon weg. - **Presence (`XmppPresenceBeacon.announce`):** de laatste presence wordt bij de volgende `announce` opnieuw verzonden (als de slide veranderd is), dus dit is grotendeels zelfherstellend. ### Trust boundary Interne logica — geen externe aanvaller. Een gewone netwerkdrop volstaat. ### Impact - Chatberichten gaan verloren met een valse bevestiging (lokale echo) — de gebruiker denkt dat ze zijn afgeleverd. - Keyshares gaan verloren — newcomers worden niet gesleuteld. - Autoriteit's ops gaan verloren — de deck-state divergeert stil. ### Oplossingsrichting 1. **Buffer outbound stanzas tijdens reconnect.** Laat `XmppSession` een korte outbound-queue bijhouden (begrensd, bijv. 64 stanzas). `sendStanza` voegt toe aan de queue als `!_live`; na een geslaagde reconnect spoelt `_reconnect` de queue naar de nieuwe transport. Bij een fail-closed (max pogingen) wordt de queue gewist. 2. **Voor chat specifiek:** markeer lokaal geëchoode berichten als "verzonden" pas nadat `sendStanza` bevestigt dat de stanza op de wire is gezet. Een bericht dat in de buffer staat, toont een "verzenden…"-indicator. Dit vereist dat `sendStanza` een boolean retourneert of een callback krijgt. 3. **Voor keyshares specifiek:** de autoriteit's `ensureKeyed` is idempotent en wordt elke `syncNow`-ronde aangeroepen — een verloren keyshare wordt op de volgende ronde opnieuw verzonden (als de newcomer nog in de approval-set staat). Dit is grotendeels zelfherstellend, maar de ronde-duur (1 seconde) betekent maximaal 1 seconde vertraging. 4. **Voor autoriteit's ops:** overweeg een bevestigings-mechanisme — de autoriteit hoort zijn eigen op terug via de MUC-reflectie; als die niet komt, is de op waarschijnlijk verloren. ### Locatie - `lib/xmpp/xmpp_session.dart` regels 590–593 (`sendStanza`), 515–526 (`_onStreamDropped` — zet `_live = false`) - `lib/xmpp/xmpp_chat.dart` regels 124–157 (`send` — lokale echo vóór `sendStanza`) - `lib/xmpp/xmpp_key_exchange.dart` regels 170–185 (`_sendKeyshare`) - `lib/xmpp/xmpp_transport.dart` regels 195–209 (`sendOp`) ### Severity HIGH — chatberichten met valse bevestiging en verloren keyshares zijn het ernstigst.
brenno 2026-08-09 20:56:27 +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#1417
No description provided.