{"record":{"id":"4504dfc2249474fa","repo":"MuntashirAkon/AppManager","slug":"corrupted-struct-sparse-detected","errorCode":null,"errorMessage":"Corrupted struct sparse detected","messagePattern":"Corrupted struct sparse detected","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java","lineNumber":923,"sourceCode":"            final Comparator<TarArchiveStructSparse> sparseHeaderComparator = (p, q) -> {\n                final Long pOffset = p.getOffset();\n                final Long qOffset = q.getOffset();\n                return pOffset.compareTo(qOffset);\n            };\n            Collections.sort(sparseHeaders, sparseHeaderComparator);\n        }\n\n        if (sparseHeaders != null) {\n            // Stream doesn't need to be closed at all as it doesn't use any resources\n            final InputStream zeroInputStream = new TarArchiveSparseZeroInputStream(); //NOSONAR\n            long offset = 0;\n            for (final TarArchiveStructSparse sparseHeader : sparseHeaders) {\n                if (sparseHeader.getOffset() == 0 && sparseHeader.getNumbytes() == 0) {\n                    break;\n                }\n\n                if ((sparseHeader.getOffset() - offset) < 0) {\n                    throw new IOException(\"Corrupted struct sparse detected\");\n                }\n\n                // only store the input streams with non-zero size\n                if ((sparseHeader.getOffset() - offset) > 0) {\n                    sparseInputStreams.add(new BoundedInputStream(zeroInputStream, sparseHeader.getOffset() - offset));\n                }\n\n                // only store the input streams with non-zero size\n                if (sparseHeader.getNumbytes() > 0) {\n                    sparseInputStreams.add(new BoundedInputStream(inputStream, sparseHeader.getNumbytes()));\n                }\n\n                offset = sparseHeader.getOffset() + sparseHeader.getNumbytes();\n            }\n        }\n\n        if (!sparseInputStreams.isEmpty()) {\n            currentSparseInputStreamIndex = 0;","sourceCodeStart":905,"sourceCodeEnd":941,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java#L905-L941","documentation":"Thrown by TarArchiveInputStream.buildSparseInputStreams when a GNU/pax sparse-file header lists a chunk whose offset is less than the current stream offset, meaning the sparse entries are not monotonically increasing. This indicates the archive's sparse metadata is corrupt, so the library refuses to build the sparse input stream list rather than produce a wrongly reassembled file.","triggerScenarios":"Reading a tar entry whose pax or old-GNU sparse headers (via paxHeaders or readOldGNUSparse) contain a sparse struct whose offset is smaller than the previous chunk's offset+size; typically a truncated, hand-edited, or maliciously crafted archive.","commonSituations":"Processing untrusted or partially downloaded tar archives; archives transferred in text mode corrupting header bytes; hand-crafted sparse entries; fuzzed/security-scanned inputs (this check exists as a hardening against crafted archives).","solutions":["Verify the archive integrity (checksum, re-download) — the file itself is likely corrupt.","Re-create the archive with a standard tool (GNU tar with -S for sparse files) instead of hand-building sparse headers.","If you control archive generation, ensure sparse offsets are strictly increasing and cover the file contiguously.","If you don't need sparse support, write the archive as a normal (non-sparse) tar entry."],"exampleFix":"// before: trusting a third-party sparse tar\new TarArchiveInputStream(new FileInputStream(f)) // throws on corrupt sparse headers\n// after: validate source first\nif (!checksumMatches(f, expectedSha256)) throw new IOException(\"archive corrupt, re-fetch\");","handlingStrategy":"validation","validationCode":"// Before reading a sparse entry, sanity-check each sparse header\nfor (TarArchiveStructSparse s : sparseHeaders) {\n    if (s.getOffset() < lastOffset) throw new IOException(\"non-monotonic sparse offset: \" + s.getOffset());\n    lastOffset = s.getOffset() + s.getNumbytes();\n}","typeGuard":null,"tryCatchPattern":"try (TarArchiveInputStream tis = new TarArchiveInputStream(in)) {\n    // read entries\n} catch (IOException e) {\n    if (e.getMessage().contains(\"Corrupted struct sparse\")) {\n        // quarantine/re-fetch archive\n    } else throw e;\n}","preventionTips":["Verify archive checksums before extraction","Re-download truncated archives instead of retrying","Avoid hand-editing tar headers","Use GNU tar -S to create valid sparse archives"],"tags":["tar","archive-corruption","input-validation","sparse-file"],"backgroundTag":"checksum-mismatch","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}