{"record":{"id":"92afda720545ff4b","repo":"juicedata/juicefs","slug":"encrypt-chunked-ciphertext-d-exceeds-capacity-d","errorCode":null,"errorMessage":"encrypt_chunked: ciphertext %d exceeds capacity %d","messagePattern":"encrypt_chunked: ciphertext (.+?) exceeds capacity (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/object/encrypt_chunked.go","lineNumber":240,"sourceCode":"\tplain := cr.pool.Get().(*[]byte)\n\tdefer cr.pool.Put(plain)\n\n\tn, readErr := io.ReadFull(cr.r, *plain)\n\tif n == 0 {\n\t\tif readErr == io.EOF || readErr == io.ErrUnexpectedEOF {\n\t\t\tcr.done = true\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\treturn 0, readErr\n\t}\n\n\tct, err := cr.enc.Encrypt((*plain)[:n])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tfixedCtLen := n + cr.overhead\n\tif len(ct) > fixedCtLen {\n\t\treturn 0, fmt.Errorf(\"encrypt_chunked: ciphertext %d exceeds capacity %d\", len(ct), fixedCtLen)\n\t}\n\n\tchunk := make([]byte, chunkHeaderSize+fixedCtLen)\n\tbinary.BigEndian.PutUint32(chunk[:chunkHeaderSize], uint32(len(ct)))\n\tcopy(chunk[chunkHeaderSize:], ct)\n\n\tcopied := copy(p, chunk)\n\tif copied < len(chunk) {\n\t\tcr.buf = chunk[copied:]\n\t}\n\n\tif readErr == io.EOF || readErr == io.ErrUnexpectedEOF {\n\t\tcr.done = true\n\t} else if readErr != nil {\n\t\treturn copied, readErr\n\t}\n\treturn copied, nil\n}","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/object/encrypt_chunked.go#L222-L258","documentation":"When encrypting a chunk, the writer assumes the ciphertext fits in n+overhead bytes (AEAD tag plus a small margin). If the encryptor returns more bytes than that reserved capacity — an internal invariant violation — the write aborts rather than produce a chunk that cannot be parsed back.","triggerScenarios":"Calling Read on a chunkedEncryptedReader whose enc.Encrypt returns ciphertext longer than plaintext+overhead, e.g. an encryptor with a larger overhead than declared at construction (chunked mode uses a fixed 16-byte overhead assumption for its algorithms).","commonSituations":"Adding a new cipher to NewDataEncryptor without updating the chunked overhead assumption; library-level bug rather than user misconfiguration; data path patched or forked.","solutions":["Do not add/modify ciphers without updating the overhead calculation in the chunked encryptor construction","Report/inspect as a bug: check the JuiceFS version and include the encrypt-algo in the report","Use a stock supported algorithm (aes256gcm-rsa, chacha20-rsa, sm4gcm) which satisfies the overhead invariant","Retry on a stock build if you are running a patched/forked client"],"exampleFix":"// before (custom build)\nreturn &dataEncryptor{keyEncryptor, 32, aead} // overhead mismatch in chunked mode\n// after\nreturn &chunkedEncryptedObject{...} with overhead matching the AEAD tag size (16)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"n, err := r.Read(buf)\nif err != nil && strings.Contains(err.Error(), \"exceeds capacity\") {\n\t// internal invariant: report bug with version + encrypt-algo; use stock build\n}","preventionTips":["Use official JuiceFS builds with supported algos only","If forking, keep the chunked overhead constant in sync with NewDataEncryptor","Add a unit test asserting Encrypt output <= plaintext+overhead for every cipher","Upgrade via supported release channels, not ad-hoc patches"],"tags":["crypto","internal-invariant","chunked-encryption"],"backgroundTag":"internal-invariant-violation","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}