{"record":{"id":"f4d6864fa626db40","repo":"rustfs/rustfs","slug":"invalid-index-chunk-type","errorCode":null,"errorMessage":"invalid index chunk type","messagePattern":"invalid index chunk type","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"warning","filePath":"crates/rio-v2/src/s2_index.rs","lineNumber":173,"sourceCode":"            if idx == 0 {\n                if info.uncompressed_offset != 0 {\n                    return true;\n                }\n                continue;\n            }\n            if info.uncompressed_offset != self.info[idx - 1].uncompressed_offset + self.est_block_uncompressed {\n                return true;\n            }\n        }\n        false\n    }\n\n    fn load(mut bytes: &[u8]) -> io::Result<Self> {\n        if bytes.len() <= SKIPPABLE_FRAME_HEADER + S2_INDEX_HEADER.len() + S2_INDEX_TRAILER.len() {\n            return Err(io::Error::new(io::ErrorKind::UnexpectedEof, \"buffer too small\"));\n        }\n        if bytes[0] != CHUNK_TYPE_INDEX {\n            return Err(io::Error::new(io::ErrorKind::InvalidData, \"invalid index chunk type\"));\n        }\n\n        let chunk_len = (bytes[1] as usize) | ((bytes[2] as usize) << 8) | ((bytes[3] as usize) << 16);\n        bytes = &bytes[SKIPPABLE_FRAME_HEADER..];\n        if bytes.len() < chunk_len {\n            return Err(io::Error::new(io::ErrorKind::UnexpectedEof, \"buffer too small\"));\n        }\n        bytes = &bytes[..chunk_len];\n\n        if !bytes.starts_with(S2_INDEX_HEADER) {\n            return Err(io::Error::new(io::ErrorKind::InvalidData, \"invalid index header\"));\n        }\n        bytes = &bytes[S2_INDEX_HEADER.len()..];\n\n        let (total_uncompressed, used) = read_varint(bytes)?;\n        if total_uncompressed < 0 {\n            return Err(io::Error::new(io::ErrorKind::InvalidData, \"invalid uncompressed size\"));\n        }","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/rustfs/rustfs/blob/9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de/crates/rio-v2/src/s2_index.rs#L155-L191","documentation":"The first byte of the index buffer must be 0x99, the s2 skippable-index chunk type that starts the 4-byte frame header; load fails with InvalidData on anything else. This check intentionally runs on both storage forms: MinIO stores the frame header-stripped, so decode_minio_index_bytes first tries the raw bytes (failing here) and then retries with restore_index_headers re-adding the frame — an expected control-flow failure internally.","triggerScenarios":"Feeding the header-stripped MinIO storage form directly to the frame parser (expected on the first internal attempt; the restored-header retry succeeds); arbitrary or JSON bytes passed as an index; the wrong byte range sliced out of the object or metadata.","commonSituations":"Interop with MinIO-written metadata whose stored form lacks the 0x99 frame header; passing a legacy JSON index; unit tests with synthetic buffers that omit the chunk-type byte.","solutions":["Use decode_minio_index_bytes, which transparently retries with restored headers, instead of parsing raw stored bytes yourself.","When validating manually, accept either form: byte 0 == 0x99 (full frame) or the stripped form that starts directly with the 's2idx' magic/varint payload.","Confirm the buffer really is the seek-index metadata value, not object data or another header."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"fn is_full_s2_frame(b: &[u8]) -> bool { b.first() == Some(&0x99) }\nfn is_stripped_s2_form(b: &[u8]) -> bool { b.starts_with(b\"s2idx\\x00\") }","typeGuard":"fn is_s2_index_payload(b: &[u8]) -> bool {\n    is_full_s2_frame(b) || is_stripped_s2_form(b)\n}","tryCatchPattern":"match decode_index(bytes) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData => {\n        // try the alternate storage form, then degrade to sequential reads\n        decode_index(&restore_index_headers(bytes)).unwrap_or(None)\n    }\n    other => other.ok(),\n}","preventionTips":["Route MinIO-stored bytes through decode_minio_index_bytes, which handles both the full-frame and header-stripped forms.","Never assume a particular storage form when interoperating; sniff the leading bytes.","Write integration tests covering both forms for every reader you ship."],"tags":["s2","compression","index","chunk-type","minio-interop","rust"],"backgroundTag":"malformed-compression-index","analyzedSha":"9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de","analyzedAt":"2026-08-16T20:34:17.560Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}