Versleutelde zip-leden alloceren vol vóór cap-check (stale commentaar blokkeert streaming-inflate) #1351
Labels
No labels
accepted
bug
declined
docs
duplicate
enhancement
good first issue
in-progress
needs-info
privacy
security
triage
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
LibreKAT/Ocideck#1351
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bevinding
FileService.decodePackageEntriesheeft twee takken voor het uitlezen van eenzip-lid:
f.writeContent(_CappedOutputStream(remaining))— deinflater streamt incrementeel naar een begrensde output, en een deflate-bom
met understated header-grootte wordt mid-decompressie gestopt
(
ExtractionLimitException). Dit is de sterke guard.f.content— decodeert de hele entry in éénkeer in het geheugen, en pas daarna wordt
extracted + raw.length > maxBytesgecheckt. Een bom die zijn uncompressed size understated alloceert dus de
volledige plaintext vóór de weigering.
De code motiveert die splitsing met:
Dat commentaar klopt niet voor de geïnstalleerde archive-versie
De repo draait op
archive: 4.0.9. Daarin doetZipFile.decompress(output)(<ref_snippet file="/Users/brennodewinter/.pub-cache/hosted/pub.dev/archive-4.0.9/lib/src/codecs/zip/zip_file.dart" lines="164-193" />) expliciet wél de AES-laag vóór het inflaten:
_rawContent = _decodeAes(_rawContent!)— ontsleutelt de gecomprimeerdepayload (begrensd door de 512 MiB input-cap).
ZLibDecoder().decodeStream(_rawContent!, output, raw: true)— streamt deinflate naar de meegegeven output.
En
ArchiveFile.writeContent(output)roeptdecompress(output)aan(<ref_snippet file="/Users/brennodewinter/.pub-cache/hosted/pub.dev/archive-4.0.9/lib/src/archive/archive_file.dart" lines="153-167" />). De streaming
writeContent-weg past de AES-laag dus wél toe. Het commentaar is verouderd of is nooit juist geweest voor 4.0.x.Oplossingsrichting
De twee takken samenvoegen: ook voor versleutelde leden
writeContent(capped)gebruiken, met dezelfde HMAC-failure-catch die er al is. De
_decodeAes-throw("macs don't match") gebeurt dan binnen
writeContenten wordt op dezelfdeplek gevangen. De inflate wordt daarna mid-stream begrensd in plaats van volledig
gealloceerd.
Inschatting: ~10-15 regels diff. De
f.content-allocatie van de heleongecomprimeerde entry verdwijnt, en daarmee het gat bij een bom met understated
header-grootte.
_decodeAesalloceert nog steeds de volledige gecomprimeerdeplaintext (onvermijdelijk: WinZip-AES vereist de volledige content voor de
HMAC), maar dat is begrensd door de 512 MiB input-cap, niet door de
ongecomprimeerde grootte — een orde-grootte beter.
Caveat
Het commentaar bestond om een reden — mogelijk was het waar voor een oudere
archive-versie. Daarom: schrijf eerst een test die een versleutelde entry via
writeContent(capped)aanbiedt en bevestigt dat (a) de bytes kloppen en (b) eenbom mid-stream stopt. Pas dan de code aan. Zonder die test herhaal je de fout
die het commentaar maakte — aannemen in plaats van verifiëren.
Herkomst
Gevonden tijdens security research naar de robustness van OciDeck bij het
openen van corrupte bestanden en resource-uitputting. De bevinding uit de
eerdere ronde ("versleutelde leden hebben niet de streaming-inflate-cap") bleek
bij nader inzien niet te kloppen voor de geïnstalleerde archive-versie — de
package stelt de streaming AES-weg wél bloot, maar de OciDeck-code gebruikt hem
niet vanwege een stale commentaar.