Recovery-service crash bij opstarten (StackOverflowError uit jsonDecode niet gevangen) #1359

Closed
opened 2026-08-07 22:25:15 +00:00 by brenno · 0 comments
Owner

Bevinding

RecoveryService.loadAll (lib/services/recovery_service.dart:257) loopt bij
het opstarten van de app en doet jsonDecode op elk .json-bestand in de
recovery-map. Er is een try/catch per bestand, wat een gewone
FormatException vangt.

Maar een StackOverflowError uit een diep-geneste JSON wordt in Dart niet
door try/catch gevangen — het is een Error die de isolate kan meenemen. Een
corrupt herstelbestand (bv. na een crash tijdens het schrijven) met diepe
nesting zou de app bij het opstarten laten crashen, en de gebruiker kan er
niet eens omheen — de recovery-service draait vóór de UI.

Waarom dit kritieker is dan #1353

#1353 gaat over jsonDecode op sidecars en grafiekdata bij het openen van een
bestand — de gebruiker moet een bestand openen om het te triggeren.

Dit is een subset van #1353, maar op een extra kritieke call-site: de
recovery-service draait bij het opstarten van de app, vóór de UI, en leest
elk .json-bestand in de recovery-map. Een corrupt herstelbestand laat de
app niet meer opstarten, en de gebruiker kan er niet omheen — hij kan niet
eens bij de instellingen om de recovery-map leeg te maken.

Hoe een corrupt herstelbestand ontstaat

  • Een crash tijdens het schrijven van een recovery-snapshot (de atomic-write
    laat normaal een .tmp achter, maar een crash op het juiste moment kan een
    half geschreven .json opleveren).
  • Een schijffout of bestandssysteemcorruptie.
  • Een kwaadwillende met schrijftoegang tot de recovery-map (lage prioriteit
    voor een lokaal-eerst app, maar niet nul).

Oplossingsrichting

Dit is een call-site van #1353: een depth-guarded JSON-parser (of pre-scan)
op de recovery-service lost het op. Maar omdat dit het opstart-pad is, verdient
het een extra verdediging: als de recovery-service ondanks de diepte-check
toch faalt, moet de app nog steeds opstarten — de recovery is een
gemaksfunctie, geen voorwaarde om te kunnen werken.

Twee lagen:

  1. Diepte-check (deelt de oplossing met #1353): pre-scan de raw string op
    nesting-diepte vóór jsonDecode, weiger boven een redelijke grens.
  2. Opstart-veilige fallback: als loadAll ondanks de diepte-check faalt,
    de recovery-map hernoemen naar een backup-naam (niet verwijderen — de
    gebruiker wil misschien iets terughalen) en de app normaal laten opstarten
    met een lege recovery-lijst.

Inschatting: ~10-15 regels voor de fallback, bovenop de gedeelde oplossing
met #1353.

Herkomst

Gevonden tijdens security research naar defense-in-depth voor OciDeck. #1353
identificeerde de JSON-diepte-limiet; dit is de call-site waar het
kritiek wordt — niet "een bestand kan niet openen" maar "de app start niet
meer op".

## Bevinding `RecoveryService.loadAll` (`lib/services/recovery_service.dart:257`) loopt bij het opstarten van de app en doet `jsonDecode` op elk `.json`-bestand in de recovery-map. Er is een try/catch per bestand, wat een gewone `FormatException` vangt. Maar een `StackOverflowError` uit een diep-geneste JSON wordt in Dart **niet** door try/catch gevangen — het is een `Error` die de isolate kan meenemen. Een corrupt herstelbestand (bv. na een crash tijdens het schrijven) met diepe nesting zou de app bij het opstarten laten crashen, en de gebruiker kan er niet eens omheen — de recovery-service draait vóór de UI. ## Waarom dit kritieker is dan #1353 #1353 gaat over `jsonDecode` op sidecars en grafiekdata bij het openen van een bestand — de gebruiker moet een bestand openen om het te triggeren. Dit is een subset van #1353, maar op een extra kritieke call-site: de recovery-service draait bij het opstarten van de app, vóór de UI, en leest elk `.json`-bestand in de recovery-map. Een corrupt herstelbestand laat de app niet meer opstarten, en de gebruiker kan er niet omheen — hij kan niet eens bij de instellingen om de recovery-map leeg te maken. ## Hoe een corrupt herstelbestand ontstaat - Een crash tijdens het schrijven van een recovery-snapshot (de atomic-write laat normaal een `.tmp` achter, maar een crash op het juiste moment kan een half geschreven `.json` opleveren). - Een schijffout of bestandssysteemcorruptie. - Een kwaadwillende met schrijftoegang tot de recovery-map (lage prioriteit voor een lokaal-eerst app, maar niet nul). ## Oplossingsrichting Dit is een call-site van #1353: een depth-guarded JSON-parser (of pre-scan) op de recovery-service lost het op. Maar omdat dit het opstart-pad is, verdient het een extra verdediging: als de recovery-service ondanks de diepte-check toch faalt, moet de app nog steeds opstarten — de recovery is een gemaksfunctie, geen voorwaarde om te kunnen werken. Twee lagen: 1. **Diepte-check** (deelt de oplossing met #1353): pre-scan de raw string op nesting-diepte vóór `jsonDecode`, weiger boven een redelijke grens. 2. **Opstart-veilige fallback**: als `loadAll` ondanks de diepte-check faalt, de recovery-map hernoemen naar een backup-naam (niet verwijderen — de gebruiker wil misschien iets terughalen) en de app normaal laten opstarten met een lege recovery-lijst. Inschatting: ~10-15 regels voor de fallback, bovenop de gedeelde oplossing met #1353. ## Herkomst Gevonden tijdens security research naar defense-in-depth voor OciDeck. #1353 identificeerde de JSON-diepte-limiet; dit is de call-site waar het kritiek wordt — niet "een bestand kan niet openen" maar "de app start niet meer op".
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#1359
No description provided.