{"record":{"id":"67edc1ee98949a2b","repo":"apache/pulsar","slug":"target-buffer-size-is-too-small","errorCode":null,"errorMessage":"Target buffer size is too small","messagePattern":"Target buffer size is too small","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-client-messagecrypto-bc/src/main/java/org/apache/pulsar/client/impl/crypto/MessageCryptoBc.java","lineNumber":596,"sourceCode":"        }\n    }\n\n    private boolean decryptData(SecretKey dataKeySecret, MessageMetadata msgMetadata,\n                                ByteBuffer payload, ByteBuffer targetBuffer) {\n        // unpack iv and encrypted data\n        byte[] iv = msgMetadata.getEncryptionParam();\n\n        GCMParameterSpec gcmParams = new GCMParameterSpec(tagLen, iv);\n        try {\n            // mark the buffers to allow resetting them in case of decryption failure\n            payload.mark();\n            targetBuffer.mark();\n\n            Cipher cipher = getAesGcmCipher();\n            cipher.init(Cipher.DECRYPT_MODE, dataKeySecret, gcmParams);\n            int maxLength = cipher.getOutputSize(payload.remaining());\n            if (targetBuffer.remaining() < maxLength) {\n                throw new IllegalArgumentException(\"Target buffer size is too small\");\n            }\n            int decryptedSize = cipher.doFinal(payload, targetBuffer);\n            targetBuffer.flip();\n            targetBuffer.limit(decryptedSize);\n            return true;\n        } catch (Exception e) {\n            // reset the buffers so that decryption can be retried with the same buffers\n            payload.reset();\n            targetBuffer.reset();\n\n            log.error().attr(\"logCtx\", logCtx).exceptionMessage(e)\n                    .log(\"Failed to decrypt message\");\n            return false;\n        }\n    }\n\n    @Override\n    public int getMaxOutputSize(int inputLen) {","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-messagecrypto-bc/src/main/java/org/apache/pulsar/client/impl/crypto/MessageCryptoBc.java#L578-L614","documentation":"MessageCryptoBc.decryptData computes cipher.getOutputSize(payload.remaining()) for AES-GCM decryption and requires the caller-provided targetBuffer to have that much remaining space; otherwise it throws IllegalArgumentException before attempting the doFinal. The plaintext is generally smaller than the ciphertext but the library reserves worst-case output size.","triggerScenarios":"Calling MessageCryptoBc.decrypt where the targetBuffer (the output buffer holding the decrypted payload) has remaining() fewer bytes than cipher.getOutputSize(payload.remaining()) for the encrypted chunk being decrypted.","commonSituations":"Consumer allocating a receive buffer exactly equal to the ciphertext length; reusing a buffer across messages where a later message's encrypted chunk is larger; custom decryption flows that pass undersized buffers instead of using the library's default handling.","solutions":["Allocate the target buffer with headroom equal to or greater than cipher.getOutputSize(encryptedLength) (i.e. at least the ciphertext size plus tag)","Clear/flip the target buffer before reuse so remaining() is maximal","If you control the call site, mirror the encrypt-side sizing logic (payload + 16-byte GCM tag)","Check that chunked-message reassembly is passing the correct per-chunk buffer rather than a fixed undersized one"],"exampleFix":"// before\nByteBuffer target = ByteBuffer.allocate(encryptedChunk.remaining());\n// after\nByteBuffer target = ByteBuffer.allocate(encryptedChunk.remaining() + 16); // worst-case GCM output","handlingStrategy":"validation","validationCode":"// Ensure the decrypt target buffer can hold worst-case output\nint needed = cipher.getOutputSize(encryptedChunk.remaining()); // at least encrypted size + tag\nif (targetBuffer.remaining() < needed) {\n    targetBuffer = ByteBuffer.allocate(needed);\n}","typeGuard":null,"tryCatchPattern":"try {\n    boolean ok = crypto.decrypt(dataKeyInfo, encryptedPayload, targetBuffer);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Target buffer\")) {\n        // reallocate with headroom and retry once\n        targetBuffer = ByteBuffer.allocate(encryptedPayload.remaining() + 16);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Size consumer decrypt buffers from the encrypted chunk length, not the expected plaintext length","Clear/flip reusable buffers before each decrypt call","Account for the 16-byte GCM tag in all buffer sizing math","Cover buffer sizing with tests using the largest configured message size"],"tags":["crypto","aes-gcm","buffer","decryption","bytebuffer"],"backgroundTag":"buffer-too-small","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}