{"record":{"id":"bdaf9b91da5f164f","repo":"juicedata/juicefs","slug":"decrypt-truncated-chunk-header","errorCode":null,"errorMessage":"Decrypt: truncated chunk header","messagePattern":"Decrypt: truncated chunk header","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/object/encrypt_chunked.go","lineNumber":160,"sourceCode":"\t\t\tr.putChunkBuf()\n\t\t}\n\t\treturn n, nil\n\t}\n\tchunkBuf := r.pool.Get().(*[]byte)\n\tdefer func() {\n\t\tif len(r.buf) == 0 {\n\t\t\tr.pool.Put(chunkBuf)\n\t\t}\n\t}()\n\n\tn, err := io.ReadFull(r.r, *chunkBuf)\n\tchunk := (*chunkBuf)[:n]\n\tif err != io.ErrUnexpectedEOF && err != nil {\n\t\treturn 0, err\n\t}\n\n\tif len(chunk) < chunkHeaderSize {\n\t\treturn 0, fmt.Errorf(\"Decrypt: truncated chunk header\")\n\t}\n\tctLen := int(binary.BigEndian.Uint32(chunk[:chunkHeaderSize]))\n\tif chunkHeaderSize+ctLen > len(chunk) {\n\t\treturn 0, fmt.Errorf(\"Decrypt: chunk data truncated: need %d, have %d\", chunkHeaderSize+ctLen, len(chunk))\n\t}\n\n\tplain, decErr := r.enc.Decrypt(chunk[chunkHeaderSize : chunkHeaderSize+ctLen])\n\tif decErr != nil {\n\t\treturn 0, fmt.Errorf(\"Decrypt: %s\", decErr)\n\t}\n\n\tif r.skip > 0 {\n\t\tskip := r.skip\n\t\tr.skip = 0\n\t\tif skip >= int64(len(plain)) {\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\tplain = plain[skip:]","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/object/encrypt_chunked.go#L142-L178","documentation":"The chunked encrypted reader reads fixed-size chunks, each starting with a 4-byte big-endian header holding the ciphertext length. If a read yields fewer bytes than that header (chunkHeaderSize), the stream is incomplete and 'Decrypt: truncated chunk header' is raised.","triggerScenarios":"Calling Read on a chunkedEncryptedObject whose source object ends (or errors with io.ErrUnexpectedEOF) before even 4 bytes of a chunk header are available — e.g. reading past a truncated object or an empty/corrupt object.","commonSituations":"Object truncated by an interrupted upload or partial sync; reading an object written by a non-chunked writer; bucket contents manually edited; offset beyond actual object size served as a fresh read.","solutions":["Re-upload or re-sync the object from a healthy replica (juicefs sync with --check-new/--check-all)","Verify the object size in the object store matches expectation; a size not congruent with chunk layout indicates truncation","If the read offset can exceed object size, clamp reads to the object length returned by Head before reading","Check for interrupted juicefs gc/sync jobs and re-run them to repair partial copies"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if off >= objSize { return io.EOF } // avoid reading past a truncated object","typeGuard":null,"tryCatchPattern":"n, err := r.Read(buf)\nif err != nil && strings.Contains(err.Error(), \"truncated chunk header\") {\n\t// object truncated: re-upload or re-sync from replica\n}","preventionTips":["Verify object sizes after backup restore (size % chunk-size layout check)","Don't kill sync/upload jobs mid-object; re-run them to completion","Clamp reads to the length reported by Head","Monitor storage backend for incomplete multipart uploads"],"tags":["crypto","decrypt","truncated-data","object-storage"],"backgroundTag":"checksum-mismatch","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}