{"record":{"id":"e1472652bde19e2c","repo":"apache/cassandra","slug":"could-not-read-encrypted-blocked-metadata-header","errorCode":null,"errorMessage":"could not read encrypted blocked metadata header","messagePattern":"could not read encrypted blocked metadata header","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/security/EncryptionUtils.java","lineNumber":145,"sourceCode":"     * Decrypt the input data, as well as manage sizing of the {@code outputBuffer}; if the buffer is not big enough,\n     * deallocate current, and allocate a large enough buffer.\n     *\n     * @return the byte buffer that was actaully written to; it may be the {@code outputBuffer} if it had enough capacity,\n     * or it may be a new, larger instance. Callers should capture the return buffer (if calling multiple times).\n     */\n    public static ByteBuffer decrypt(ReadableByteChannel channel, ByteBuffer outputBuffer, boolean allowBufferResize, Cipher cipher) throws IOException\n    {\n        ByteBuffer metadataBuffer = reusableBuffers.get();\n        if (metadataBuffer.capacity() < ENCRYPTED_BLOCK_HEADER_SIZE)\n        {\n            metadataBuffer = ByteBufferUtil.ensureCapacity(metadataBuffer, ENCRYPTED_BLOCK_HEADER_SIZE, true);\n            reusableBuffers.set(metadataBuffer);\n        }\n\n        metadataBuffer.position(0).limit(ENCRYPTED_BLOCK_HEADER_SIZE);\n        channel.read(metadataBuffer);\n        if (metadataBuffer.remaining() < ENCRYPTED_BLOCK_HEADER_SIZE)\n            throw new IllegalStateException(\"could not read encrypted blocked metadata header\");\n        int encryptedLength = metadataBuffer.getInt();\n        // this is the length of the compressed data\n        int plainTextLength = metadataBuffer.getInt();\n\n        outputBuffer = ByteBufferUtil.ensureCapacity(outputBuffer, Math.max(plainTextLength, encryptedLength), allowBufferResize);\n        outputBuffer.position(0).limit(encryptedLength);\n        channel.read(outputBuffer);\n\n        ByteBuffer dupe = outputBuffer.duplicate();\n        dupe.clear();\n\n        try\n        {\n            cipher.doFinal(outputBuffer, dupe);\n        }\n        catch (ShortBufferException | IllegalBlockSizeException | BadPaddingException e)\n        {\n            throw new IOException(\"failed to decrypt commit log block\", e);","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/security/EncryptionUtils.java#L127-L163","documentation":"EncryptionUtils.decrypt reads the fixed-size encrypted block metadata header (two ints: encrypted and plaintext lengths) from the channel. If the channel yields fewer bytes than ENCRYPTED_BLOCK_HEADER_SIZE, the data is truncated/corrupt and IllegalStateException is thrown.","triggerScenarios":"decrypt() reading from a commit log segment that ends before a complete 16-byte header — truncated file, zero-padded tail, or reading past the last valid block.","commonSituations":"Disk-full during commit log write, abrupt crash leaving a partially written segment, or a file copied/truncated at the wrong size.","solutions":["Treat the segment as truncated: verify file size against the segment's recorded sync position / commit log metadata.","Restore the segment from backup or rely on unflushed data loss handling (commit logs are restart-safe; partial tails are skipped at startup).","Check storage health (disk-full, filesystem errors) that could have truncated writes.","If in tests, ensure all encrypted blocks were fully flushed and the channel position is valid before decrypting."],"exampleFix":"// before: reading a partially written segment tail\nByteBuffer out = EncryptionUtils.decrypt(channel, null, true, reusableBuffers);\n// after: bound reads to the committed length\nlong readableEnd = Math.min(channel.size(), committedSyncPosition);\nif (channel.position() + EncryptionUtils.ENCRYPTED_BLOCK_HEADER_SIZE > readableEnd) return null; // no more complete blocks\nByteBuffer out = EncryptionUtils.decrypt(channel, null, true, reusableBuffers);","handlingStrategy":"validation","validationCode":"long remaining = committedEnd - channel.position();\nif (remaining < EncryptionUtils.ENCRYPTED_BLOCK_HEADER_SIZE)\n    return null; // truncated tail: no complete block left, stop reading instead of decrypting","typeGuard":null,"tryCatchPattern":"try {\n    ByteBuffer out = EncryptionUtils.decrypt(channel, null, true, reusableBuffers);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"metadata header\")) {\n        // truncated/corrupt segment: mark segment end, stop replay\n    }\n}","preventionTips":["Never truncate commit log segments externally","Monitor disk-full conditions on the commitlog volume","Only read encrypted segments up to their recorded sync position"],"tags":["encryption","commitlog","corruption","truncated"],"backgroundTag":"unexpected-response-shape","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}