{"record":{"id":"0666b190a473e394","repo":"NationalSecurityAgency/ghidra","slug":"decompression-limit-exceeded","errorCode":null,"errorMessage":"Decompression limit exceeded: {}","messagePattern":"Decompression limit exceeded: (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"GPL/DMG/src/dmg/java/mobiledevices/dmg/reader/DmgFileReader.java","lineNumber":310,"sourceCode":"\t\t\ttry {\n\t\t\t\tsuper.close();\n\t\t\t}\n\t\t\tfinally {\n\t\t\t\t// Cleanup temporary file input stream and remove file\n\t\t\t\tin.close();\n\t\t\t\tif (tempCompressedFile != null) {\n\t\t\t\t\ttempCompressedFile.delete();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic int read(byte[] b, int off, int length) throws IOException {\n\t\t\tif (length == 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (readCount >= readLimit) {\n\t\t\t\tthrow new IOException(\"Decompression limit exceeded: \" + readLimit);\n\t\t\t}\n\t\t\t// Limit read length to avoid exceeding readLimit\n\t\t\tint limit = Math.min(readLimit - readCount, length);\n\t\t\tint count = super.read(b, off, limit);\n\t\t\tif (count > 0) {\n\t\t\t\treadCount += count;\n\t\t\t}\n\t\t\treturn count;\n\t\t}\n\t}\n\n\tpublic List<String> getInfo(String path) {\n\t\tif (path != null) {\n\t\t\tDmgInfoGenerator info = new DmgInfoGenerator(this, path, parser);\n\t\t\treturn info.getInformation();\n\t\t}\n\t\treturn null;\n\t}","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/GPL/DMG/src/dmg/java/mobiledevices/dmg/reader/DmgFileReader.java#L292-L328","documentation":"RestrictedInflaterInputStream wraps an inflater (zlib/deflate) with a hard readLimit to prevent decompression bombs. Each read() increments readCount and, if readCount already meets readLimit, throws before reading more. It is a deliberate safety cap supplied to the constructor (RestrictedInflaterInputStream(file, readLimit) or (byte[], readLimit)).","triggerScenarios":"Decompressing a DMG block whose inflated output exceeds the readLimit passed when constructing RestrictedInflaterInputStream. The limit bounds total bytes read from the inflated stream, so a genuinely large (or malicious zip-bomb-like) block trips it.","commonSituations":"A legitimate but large compressed block whose decompressed size exceeds the configured cap; a malicious/adversarial DMG crafted as a decompression bomb; the readLimit was set too low for the expected image size; counting logic accumulating more than expected due to repeated reads.","solutions":["Raise readLimit to comfortably exceed the expected inflated size of the DMG block being processed.","Verify the DMG block is not a decompression bomb by comparing its compressed size to the declared uncompressed size before decompressing.","Confirm the readLimit is being passed in bytes (not a smaller unit) and matches the block's declared output size.","If the limit is intentional policy, treat this as expected behavior and abort the untrusted image."],"exampleFix":"// before\nnew RestrictedInflaterInputStream(compressedBlock, 1024 * 1024);\n\n// after - size the limit to the declared uncompressed block size\nint limit = Math.toIntExact(Math.addExact(declaredUncompressedSize, safetyMargin));\nnew RestrictedInflaterInputStream(compressedBlock, limit);","handlingStrategy":"validation","validationCode":"// Size the decompression cap to the declared uncompressed block size plus margin\nint safeLimit = Math.toIntExact(Math.addExact(declaredUncompressedSize, safetyMarginBytes));\nif (safeLimit <= 0) {\n    throw new IOException(\"Invalid decompression limit derived from declared size \" + declaredUncompressedSize);\n}\nnew RestrictedInflaterInputStream(compressedBlock, safeLimit);","typeGuard":"boolean limitCoversExpected(int readLimit, long declaredUncompressed) {\n    return readLimit > 0 && ((long) readLimit) >= declaredUncompressed;\n}","tryCatchPattern":"try {\n    int n = stream.read(buf, off, len);\n} catch (IOException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Decompression limit exceeded\")) {\n        // Treat as policy/security abort for untrusted input; do not silently raise the cap\n        throw new IOException(\"Decompression bomb or unexpectedly large block; aborting\", e);\n    }\n    throw e;\n}","preventionTips":["Set readLimit comfortably above each block's declared uncompressed size.","Compare compressed vs. declared uncompressed size to detect bombs before decompressing.","Keep a safety margin on the cap to absorb minor over-reads.","For untrusted inputs, treat the limit as a hard security boundary, not a nuisance."],"tags":["dmg","decompression","zip-bomb","security","limits"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}