VYPR
Unrated severityNVD Advisory· Published May 28, 2026

CVE-2026-41565

CVE-2026-41565

Description

CryptX versions before 0.088_001 for Perl have a stack buffer overflow in four AEAD decrypt_verify helpers.

The gcm_decrypt_verify, ccm_decrypt_verify, chacha20poly1305_decrypt_verify and eax_decrypt_verify XS routines copied the caller-supplied authentication tag into a fixed 144-byte stack buffer (MAXBLOCKSIZE) without checking the supplied length. A longer tag overwrites the stack past the buffer. Version 0.088 added the clamp to gcm_decrypt_verify, and 0.088_001 added it to the other three.

Any caller of an affected helper that forwards an attacker-controlled tag longer than the buffer can trigger the overflow.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A stack buffer overflow in CryptX AEAD decrypt_verify helpers allows arbitrary code execution via an oversized authentication tag.

Vulnerability

Affected Perl CryptX versions before 0.088_001 contain a stack buffer overflow in four AEAD decrypt_verify XS routines: gcm_decrypt_verify, ccm_decrypt_verify, chacha20poly1305_decrypt_verify, and eax_decrypt_verify. These functions copy the caller-supplied authentication tag into a fixed 144-byte stack buffer (MAXBLOCKSIZE) without validating the tag length. Versions 0.088 and earlier are vulnerable; version 0.088 patched GCM, and 0.088_001 fixed the remaining three [1][2].

Exploitation

An attacker can trigger the overflow by providing a tag longer than 144 bytes to any of the affected decrypt_verify calls. No authentication or prior access is required if the vulnerable code is reachable via an application that forwards untrusted tag data (e.g., from network input). The attacker only needs to control the tag argument [1][2].

Impact

Successful exploitation overwrites the stack beyond the tag buffer, potentially allowing arbitrary code execution with the privileges of the Perl process using CryptX. The crash is immediate with oversized tags, but controlled manipulation may yield full remote code execution or information disclosure [1][2].

Mitigation

Upgrade to CryptX 0.088_001 or later (released around May 2026). This version adds explicit tag-length clamping in all four affected decryption routines [1][2]. No workaround is available for unpatched versions; tags must never be trusted before this fix. If upgrading is not immediately possible, applications should validate tag lengths before passing them to decrypt_verify. The CVE is not listed on CISA KEV as of publication [3].

AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

2
7e56347d420a

small improvements Crypt::AuthEnc::*

https://github.com/DCIT/perl-CryptXKarel MikoApr 27, 2026via nvd-ref
15 files changed · +446 145
  • CryptX.xs+20 5 modified
    @@ -48,11 +48,26 @@ STATIC int cryptx_internal_input_has_no_payload(const char *in, STRLEN len) {
     typedef adler32_state           *Crypt__Checksum__Adler32;
     typedef crc32_state             *Crypt__Checksum__CRC32;
     
    -typedef ccm_state               *Crypt__AuthEnc__CCM;
    -typedef eax_state               *Crypt__AuthEnc__EAX;
    -typedef gcm_state               *Crypt__AuthEnc__GCM;
    -typedef chacha20poly1305_state  *Crypt__AuthEnc__ChaCha20Poly1305;
    -typedef ocb3_state              *Crypt__AuthEnc__OCB;
    +typedef struct cryptx_authenc_ccm_struct {
    +  ccm_state state;
    +  int finalized;
    +} *Crypt__AuthEnc__CCM;
    +typedef struct cryptx_authenc_eax_struct {
    +  eax_state state;
    +  int finalized;
    +} *Crypt__AuthEnc__EAX;
    +typedef struct cryptx_authenc_gcm_struct {
    +  gcm_state state;
    +  int finalized;
    +} *Crypt__AuthEnc__GCM;
    +typedef struct cryptx_authenc_chacha20poly1305_struct {
    +  chacha20poly1305_state state;
    +  int finalized;
    +} *Crypt__AuthEnc__ChaCha20Poly1305;
    +typedef struct cryptx_authenc_ocb_struct {
    +  ocb3_state state;
    +  int finalized;
    +} *Crypt__AuthEnc__OCB;
     
     typedef chacha_state            *Crypt__Stream__ChaCha;
     typedef salsa20_state           *Crypt__Stream__Salsa20;
    
  • inc/CryptX_AuthEnc_CCM.xs.inc+54 26 modified
    @@ -24,23 +24,26 @@ new(Class, char * cipher_name, SV * key, SV * nonce, SV * adata, int tag_len, in
             h = (unsigned char *) SvPVbyte(adata, h_len);
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if (id == -1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if (id == -1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
     
    -        Newz(0, RETVAL, 1, ccm_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_ccm_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
     
    -        rv = ccm_init(RETVAL, id, k, (int)k_len, (int)pt_len, (int)tag_len, (int)h_len); /* XXX-TODO why int? */
    +        rv = ccm_init(&RETVAL->state, id, k, (int)k_len, (int)pt_len, (int)tag_len, (int)h_len); /* XXX-TODO why int? */
             if (rv != CRYPT_OK) {
    +          zeromem(RETVAL, sizeof(*RETVAL));
               Safefree(RETVAL);
               croak("FATAL: ccm_init failed: %s", error_to_string(rv));
             }
    -        rv = ccm_add_nonce(RETVAL, n, (unsigned long)n_len);
    +        rv = ccm_add_nonce(&RETVAL->state, n, (unsigned long)n_len);
             if (rv != CRYPT_OK) {
    +          zeromem(RETVAL, sizeof(*RETVAL));
               Safefree(RETVAL);
               croak("FATAL: ccm_add_nonce failed: %s", error_to_string(rv));
             }
    -        rv = ccm_add_aad(RETVAL, h, (unsigned long)h_len);
    +        rv = ccm_add_aad(&RETVAL->state, h, (unsigned long)h_len);
             if (rv != CRYPT_OK) {
    +          zeromem(RETVAL, sizeof(*RETVAL));
               Safefree(RETVAL);
               croak("FATAL: ccm_add_aad failed: %s", error_to_string(rv));
             }
    @@ -51,14 +54,17 @@ new(Class, char * cipher_name, SV * key, SV * nonce, SV * adata, int tag_len, in
     void
     DESTROY(Crypt::AuthEnc::CCM self)
         CODE:
    -        Safefree(self);
    +        if (self) {
    +          zeromem(self, sizeof(*self));
    +          Safefree(self);
    +        }
     
     Crypt::AuthEnc::CCM
     clone(Crypt::AuthEnc::CCM self)
         CODE:
    -        Newz(0, RETVAL, 1, ccm_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_ccm_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
    -        Copy(self, RETVAL, 1, ccm_state);
    +        Copy(self, RETVAL, 1, struct cryptx_authenc_ccm_struct);
         OUTPUT:
             RETVAL
     
    @@ -70,6 +76,7 @@ encrypt_add(Crypt::AuthEnc::CCM self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -79,7 +86,7 @@ encrypt_add(Crypt::AuthEnc::CCM self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = ccm_process(self, in_data, (unsigned long)in_data_len, out_data, CCM_ENCRYPT);
    +          rv = ccm_process(&self->state, in_data, (unsigned long)in_data_len, out_data, CCM_ENCRYPT);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: ccm_process failed: %s", error_to_string(rv));
    @@ -97,6 +104,7 @@ decrypt_add(Crypt::AuthEnc::CCM self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -106,7 +114,7 @@ decrypt_add(Crypt::AuthEnc::CCM self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = ccm_process(self, out_data, (unsigned long)in_data_len, in_data, CCM_DECRYPT);
    +          rv = ccm_process(&self->state, out_data, (unsigned long)in_data_len, in_data, CCM_DECRYPT);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: ccm_process failed: %s", error_to_string(rv));
    @@ -124,8 +132,10 @@ encrypt_done(Crypt::AuthEnc::CCM self)
             unsigned char tag[MAXBLOCKSIZE];
             unsigned long tag_len = MAXBLOCKSIZE;
     
    -        rv = ccm_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = ccm_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: ccm_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
         }
     
    @@ -139,8 +149,10 @@ decrypt_done(Crypt::AuthEnc::CCM self, ...)
             STRLEN expected_tag_len;
             unsigned char *expected_tag;
     
    -        rv = ccm_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = ccm_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: ccm_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             if (items == 1) {
               XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
             }
    @@ -150,7 +162,7 @@ decrypt_done(Crypt::AuthEnc::CCM self, ...)
               if (expected_tag_len!=tag_len) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
    -          else if (memNE(expected_tag, tag, tag_len)) {
    +          else if (mem_neq(expected_tag, tag, tag_len)) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
               else {
    @@ -169,13 +181,19 @@ ccm_encrypt_authenticate(char *cipher_name, SV *key, SV *nonce, SV *header, unsi
             unsigned char tag[MAXBLOCKSIZE];
             SV *output;
     
    -        if (SvPOK_spec(key))       k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK_spec(nonce))     n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK_spec(plaintext)) pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    -        if (SvPOK_spec(header))    h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(plaintext)) croak("FATAL: plaintext must be string/buffer scalar");
    +        pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, pt_len > 0 ? pt_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, pt_len);
    @@ -203,19 +221,29 @@ ccm_decrypt_verify(char *cipher_name, SV *key, SV *nonce, SV *header, SV *cipher
             unsigned long tag_len;
             SV *output;
     
    -        if (SvPOK(key))        k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))      n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(ciphertext)) ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    -        if (SvPOK(tagsv))      t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    -        if (SvPOK(header))     h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(ciphertext)) croak("FATAL: ciphertext must be string/buffer scalar");
    +        ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    +        if (!SvPOK_spec(tagsv)) croak("FATAL: tag must be string/buffer scalar");
    +        t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, ct_len > 0 ? ct_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, ct_len);
    -        tag_len = (unsigned long)t_len;
    -        Copy(t, tag, t_len, unsigned char);
    +        /* Clamp malformed tags to the stack buffer size before copying. */
    +        tag_len = (unsigned long)(t_len > sizeof(tag) ? sizeof(tag) : t_len);
    +        if (tag_len > 0) {
    +          Copy(t, tag, tag_len, unsigned char);
    +        }
     
             rv = ccm_memory(id, k, (unsigned long)k_len, NULL, n, (unsigned long)n_len, h, (unsigned long)h_len,
                             (unsigned char *)SvPVX(output), (unsigned long)ct_len, ct, tag, &tag_len, CCM_DECRYPT);
    
  • inc/CryptX_AuthEnc_ChaCha20Poly1305.xs.inc+56 26 modified
    @@ -17,18 +17,20 @@ new(Class, SV * key, SV * nonce = NULL)
               iv = (unsigned char *) SvPVbyte(nonce, iv_len);
             }
     
    -        Newz(0, RETVAL, 1, chacha20poly1305_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_chacha20poly1305_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
     
    -        rv = chacha20poly1305_init(RETVAL, k, (unsigned long)k_len);
    +        rv = chacha20poly1305_init(&RETVAL->state, k, (unsigned long)k_len);
             if (rv != CRYPT_OK) {
    +          zeromem(RETVAL, sizeof(*RETVAL));
               Safefree(RETVAL);
               croak("FATAL: chacha20poly1305_init failed: %s", error_to_string(rv));
             }
     
             if (iv && iv_len > 0) {
    -          rv = chacha20poly1305_setiv(RETVAL, iv, (unsigned long)iv_len);
    +          rv = chacha20poly1305_setiv(&RETVAL->state, iv, (unsigned long)iv_len);
               if (rv != CRYPT_OK) {
    +            zeromem(RETVAL, sizeof(*RETVAL));
                 Safefree(RETVAL);
                 croak("FATAL: chacha20poly1305_setiv failed: %s", error_to_string(rv));
               }
    @@ -40,14 +42,17 @@ new(Class, SV * key, SV * nonce = NULL)
     void
     DESTROY(Crypt::AuthEnc::ChaCha20Poly1305 self)
         CODE:
    -        Safefree(self);
    +        if (self) {
    +          zeromem(self, sizeof(*self));
    +          Safefree(self);
    +        }
     
     Crypt::AuthEnc::ChaCha20Poly1305
     clone(Crypt::AuthEnc::ChaCha20Poly1305 self)
         CODE:
    -        Newz(0, RETVAL, 1, chacha20poly1305_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_chacha20poly1305_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
    -        Copy(self, RETVAL, 1, chacha20poly1305_state);
    +        Copy(self, RETVAL, 1, struct cryptx_authenc_chacha20poly1305_struct);
         OUTPUT:
             RETVAL
     
    @@ -61,7 +66,8 @@ set_iv(Crypt::AuthEnc::ChaCha20Poly1305 self, SV * nonce)
     
             if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
             iv = (unsigned char *) SvPVbyte(nonce, iv_len);
    -        rv = chacha20poly1305_setiv(self, iv, (unsigned long)iv_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = chacha20poly1305_setiv(&self->state, iv, (unsigned long)iv_len);
             if (rv != CRYPT_OK) croak("FATAL: chacha20poly1305_setiv failed: %s", error_to_string(rv));
             XPUSHs(ST(0)); /* return self */;
         }
    @@ -76,7 +82,8 @@ set_iv_rfc7905(Crypt::AuthEnc::ChaCha20Poly1305 self, SV * nonce, UV seqnum)
     
             if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
             iv = (unsigned char *) SvPVbyte(nonce, iv_len);
    -        rv = chacha20poly1305_setiv_rfc7905(self, iv, (unsigned long)iv_len, (ulong64)seqnum);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = chacha20poly1305_setiv_rfc7905(&self->state, iv, (unsigned long)iv_len, (ulong64)seqnum);
             if (rv != CRYPT_OK) croak("FATAL: chacha20poly1305_setiv_rfc7905 failed: %s", error_to_string(rv));
             XPUSHs(ST(0)); /* return self */
         }
    @@ -89,8 +96,9 @@ adata_add(Crypt::AuthEnc::ChaCha20Poly1305 self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
    -        rv = chacha20poly1305_add_aad(self, in_data, (unsigned long)in_data_len);
    +        rv = chacha20poly1305_add_aad(&self->state, in_data, (unsigned long)in_data_len);
             if (rv != CRYPT_OK) croak("FATAL: chacha20poly1305_add_aad failed: %s", error_to_string(rv));
             XPUSHs(ST(0)); /* return self */
         }
    @@ -103,6 +111,7 @@ decrypt_add(Crypt::AuthEnc::ChaCha20Poly1305 self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -112,7 +121,7 @@ decrypt_add(Crypt::AuthEnc::ChaCha20Poly1305 self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = chacha20poly1305_decrypt(self, in_data, (unsigned long)in_data_len, out_data);
    +          rv = chacha20poly1305_decrypt(&self->state, in_data, (unsigned long)in_data_len, out_data);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: chacha20poly1305_decrypt failed: %s", error_to_string(rv));
    @@ -130,6 +139,7 @@ encrypt_add(Crypt::AuthEnc::ChaCha20Poly1305 self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -139,7 +149,7 @@ encrypt_add(Crypt::AuthEnc::ChaCha20Poly1305 self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = chacha20poly1305_encrypt(self, in_data, (unsigned long)in_data_len, out_data);
    +          rv = chacha20poly1305_encrypt(&self->state, in_data, (unsigned long)in_data_len, out_data);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: chacha20poly1305_encrypt failed: %s", error_to_string(rv));
    @@ -157,8 +167,10 @@ encrypt_done(Crypt::AuthEnc::ChaCha20Poly1305 self)
             unsigned char tag[MAXBLOCKSIZE];
             unsigned long tag_len = sizeof(tag);
     
    -        rv = chacha20poly1305_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = chacha20poly1305_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: chacha20poly1305_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
         }
     
    @@ -172,8 +184,10 @@ decrypt_done(Crypt::AuthEnc::ChaCha20Poly1305 self, ...)
             STRLEN expected_tag_len;
             unsigned char *expected_tag;
     
    -        rv = chacha20poly1305_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = chacha20poly1305_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: chacha20poly1305_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             if (items == 1) {
               XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
             }
    @@ -183,7 +197,7 @@ decrypt_done(Crypt::AuthEnc::ChaCha20Poly1305 self, ...)
               if (expected_tag_len!=tag_len) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
    -          else if (memNE(expected_tag, tag, tag_len)) {
    +          else if (mem_neq(expected_tag, tag, tag_len)) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
               else {
    @@ -203,10 +217,16 @@ chacha20poly1305_encrypt_authenticate(SV *key, SV *nonce, SV *header, SV *plaint
             unsigned long tag_len = sizeof(tag);
             SV *output;
     
    -        if (SvPOK(key))       k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))     n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(plaintext)) pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    -        if (SvPOK(header))    h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(plaintext)) croak("FATAL: plaintext must be string/buffer scalar");
    +        pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             output = NEWSV(0, pt_len > 0 ? pt_len : 1); /* avoid zero! */
             SvPOK_only(output);
    @@ -218,7 +238,7 @@ chacha20poly1305_encrypt_authenticate(SV *key, SV *nonce, SV *header, SV *plaint
     
             if (rv != CRYPT_OK) {
               SvREFCNT_dec(output);
    -          croak("FATAL: ccm_memory failed: %s", error_to_string(rv));
    +          croak("FATAL: chacha20poly1305_memory failed: %s", error_to_string(rv));
             }
             XPUSHs(sv_2mortal(output));
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
    @@ -235,17 +255,27 @@ chacha20poly1305_decrypt_verify(SV *key, SV *nonce, SV *header, SV *ciphertext,
             unsigned long tag_len;
             SV *output;
     
    -        if (SvPOK(key))        k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))      n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(ciphertext)) ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    -        if (SvPOK(tagsv))      t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    -        if (SvPOK(header))     h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(ciphertext)) croak("FATAL: ciphertext must be string/buffer scalar");
    +        ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    +        if (!SvPOK_spec(tagsv)) croak("FATAL: tag must be string/buffer scalar");
    +        t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             output = NEWSV(0, ct_len > 0 ? ct_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, ct_len);
    -        tag_len = (unsigned long)t_len;
    -        Copy(t, tag, t_len, unsigned char);
    +        /* Clamp malformed tags to the stack buffer size before copying. */
    +        tag_len = (unsigned long)(t_len > sizeof(tag) ? sizeof(tag) : t_len);
    +        if (tag_len > 0) {
    +          Copy(t, tag, tag_len, unsigned char);
    +        }
     
             rv = chacha20poly1305_memory(k, (unsigned long)k_len, n, (unsigned long)n_len, h, (unsigned long)h_len,
                                          ct, (unsigned long)ct_len, (unsigned char *)SvPVX(output), tag, &tag_len,
    
  • inc/CryptX_AuthEnc_EAX.xs.inc+53 26 modified
    @@ -24,13 +24,14 @@ new(Class, char * cipher_name, SV * key, SV * nonce, SV * adata=&PL_sv_undef)
             }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if (id == -1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if (id == -1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
     
    -        Newz(0, RETVAL, 1, eax_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_eax_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
     
    -        rv = eax_init(RETVAL, id, k, (unsigned long)k_len, n, (unsigned long)n_len, h, (unsigned long)h_len);
    +        rv = eax_init(&RETVAL->state, id, k, (unsigned long)k_len, n, (unsigned long)n_len, h, (unsigned long)h_len);
             if (rv != CRYPT_OK) {
    +          zeromem(RETVAL, sizeof(*RETVAL));
               Safefree(RETVAL);
               croak("FATAL: eax setup failed: %s", error_to_string(rv));
             }
    @@ -41,14 +42,17 @@ new(Class, char * cipher_name, SV * key, SV * nonce, SV * adata=&PL_sv_undef)
     void
     DESTROY(Crypt::AuthEnc::EAX self)
         CODE:
    -        Safefree(self);
    +        if (self) {
    +          zeromem(self, sizeof(*self));
    +          Safefree(self);
    +        }
     
     Crypt::AuthEnc::EAX
     clone(Crypt::AuthEnc::EAX self)
         CODE:
    -        Newz(0, RETVAL, 1, eax_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_eax_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
    -        Copy(self, RETVAL, 1, eax_state);
    +        Copy(self, RETVAL, 1, struct cryptx_authenc_eax_struct);
         OUTPUT:
             RETVAL
     
    @@ -60,6 +64,7 @@ encrypt_add(Crypt::AuthEnc::EAX self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -69,7 +74,7 @@ encrypt_add(Crypt::AuthEnc::EAX self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = eax_encrypt(self, in_data, out_data, (unsigned long)in_data_len);
    +          rv = eax_encrypt(&self->state, in_data, out_data, (unsigned long)in_data_len);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: eax_encrypt failed: %s", error_to_string(rv));
    @@ -87,6 +92,7 @@ decrypt_add(Crypt::AuthEnc::EAX self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -96,7 +102,7 @@ decrypt_add(Crypt::AuthEnc::EAX self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = eax_decrypt(self, in_data, out_data, (unsigned long)in_data_len);
    +          rv = eax_decrypt(&self->state, in_data, out_data, (unsigned long)in_data_len);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: eax_decrypt failed: %s", error_to_string(rv));
    @@ -114,8 +120,10 @@ encrypt_done(Crypt::AuthEnc::EAX self)
             unsigned char tag[MAXBLOCKSIZE];
             unsigned long tag_len = sizeof(tag);
     
    -        rv = eax_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = eax_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: eax_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
         }
     
    @@ -129,8 +137,10 @@ decrypt_done(Crypt::AuthEnc::EAX self, ...)
             STRLEN expected_tag_len;
             unsigned char *expected_tag;
     
    -        rv = eax_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = eax_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: eax_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             if (items == 1) {
               XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
             }
    @@ -140,7 +150,7 @@ decrypt_done(Crypt::AuthEnc::EAX self, ...)
               if (expected_tag_len!=tag_len) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
    -          else if (memNE(expected_tag, tag, tag_len)) {
    +          else if (mem_neq(expected_tag, tag, tag_len)) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
               else {
    @@ -156,8 +166,9 @@ adata_add(Crypt::AuthEnc::EAX self, SV * adata)
             STRLEN h_len;
             int rv;
             unsigned char *h;
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             h = (unsigned char *)SvPVbyte(adata, h_len);
    -        rv = eax_addheader(self, h, (unsigned long)h_len);
    +        rv = eax_addheader(&self->state, h, (unsigned long)h_len);
             if (rv != CRYPT_OK) croak("FATAL: eax_addheader failed: %s", error_to_string(rv));
             XPUSHs(ST(0)); /* return self */
         }
    @@ -173,13 +184,19 @@ eax_encrypt_authenticate(char *cipher_name, SV *key, SV *nonce, SV *header, SV *
             unsigned long tag_len = sizeof(tag);
             SV *output;
     
    -        if (SvPOK(key))       k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))     n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(plaintext)) pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    -        if (SvPOK(header))    h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(plaintext)) croak("FATAL: plaintext must be string/buffer scalar");
    +        pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, pt_len > 0 ? pt_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, pt_len);
    @@ -190,7 +207,7 @@ eax_encrypt_authenticate(char *cipher_name, SV *key, SV *nonce, SV *header, SV *
     
             if (rv != CRYPT_OK) {
               SvREFCNT_dec(output);
    -          croak("FATAL: ccm_memory failed: %s", error_to_string(rv));
    +          croak("FATAL: eax_encrypt_authenticate_memory failed: %s", error_to_string(rv));
             }
             XPUSHs(sv_2mortal(output));
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
    @@ -207,19 +224,29 @@ eax_decrypt_verify(char *cipher_name, SV *key, SV *nonce, SV *header, SV *cipher
             unsigned long tag_len;
             SV *output;
     
    -        if (SvPOK(key))        k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))      n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(ciphertext)) ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    -        if (SvPOK(tagsv))      t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    -        if (SvPOK(header))     h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(ciphertext)) croak("FATAL: ciphertext must be string/buffer scalar");
    +        ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    +        if (!SvPOK_spec(tagsv)) croak("FATAL: tag must be string/buffer scalar");
    +        t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, ct_len > 0 ? ct_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, ct_len);
    -        tag_len = (unsigned long)t_len;
    -        Copy(t, tag, t_len, unsigned char);
    +        /* Clamp malformed tags to the stack buffer size before copying. */
    +        tag_len = (unsigned long)(t_len > sizeof(tag) ? sizeof(tag) : t_len);
    +        if (tag_len > 0) {
    +          Copy(t, tag, tag_len, unsigned char);
    +        }
     
             rv = eax_decrypt_verify_memory(id, k, (unsigned long)k_len, n, (unsigned long)n_len, h, (unsigned long)h_len,
                                            ct, (unsigned long)ct_len, (unsigned char *)SvPVX(output), tag, tag_len, &stat);
    
  • inc/CryptX_AuthEnc_GCM.xs.inc+55 28 modified
    @@ -18,20 +18,22 @@ new(Class, char * cipher_name, SV * key, SV * nonce = NULL)
             }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if (id == -1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if (id == -1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
     
    -        Newz(0, RETVAL, 1, gcm_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_gcm_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
     
    -        rv = gcm_init(RETVAL, id, k, (unsigned long)k_len);
    +        rv = gcm_init(&RETVAL->state, id, k, (unsigned long)k_len);
             if (rv != CRYPT_OK) {
    +          zeromem(RETVAL, sizeof(*RETVAL));
               Safefree(RETVAL);
               croak("FATAL: gcm_init failed: %s", error_to_string(rv));
             }
     
             if (iv && iv_len > 0) {
    -          rv = gcm_add_iv(RETVAL, iv, (unsigned long)iv_len);
    +          rv = gcm_add_iv(&RETVAL->state, iv, (unsigned long)iv_len);
               if (rv != CRYPT_OK) {
    +            zeromem(RETVAL, sizeof(*RETVAL));
                 Safefree(RETVAL);
                 croak("FATAL: gcm_add_iv failed: %s", error_to_string(rv));
               }
    @@ -43,14 +45,17 @@ new(Class, char * cipher_name, SV * key, SV * nonce = NULL)
     void
     DESTROY(Crypt::AuthEnc::GCM self)
         CODE:
    -        Safefree(self);
    +        if (self) {
    +          zeromem(self, sizeof(*self));
    +          Safefree(self);
    +        }
     
     Crypt::AuthEnc::GCM
     clone(Crypt::AuthEnc::GCM self)
         CODE:
    -        Newz(0, RETVAL, 1, gcm_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_gcm_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
    -        Copy(self, RETVAL, 1, gcm_state);
    +        Copy(self, RETVAL, 1, struct cryptx_authenc_gcm_struct);
         OUTPUT:
             RETVAL
     
    @@ -59,8 +64,9 @@ reset(Crypt::AuthEnc::GCM self)
         PPCODE:
         {
             int rv;
    -        rv = gcm_reset(self);
    +        rv = gcm_reset(&self->state);
             if (rv != CRYPT_OK) croak("FATAL: gcm_reset failed: %s", error_to_string(rv));
    +        self->finalized = 0;
             XPUSHs(ST(0)); /* return self */
         }
     
    @@ -72,6 +78,7 @@ encrypt_add(Crypt::AuthEnc::GCM self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -82,7 +89,7 @@ encrypt_add(Crypt::AuthEnc::GCM self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = gcm_process(self, in_data, (unsigned long)in_data_len, out_data, GCM_ENCRYPT);
    +          rv = gcm_process(&self->state, in_data, (unsigned long)in_data_len, out_data, GCM_ENCRYPT);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: encrypt_add/gcm_process failed: %s", error_to_string(rv));
    @@ -100,8 +107,9 @@ iv_add(Crypt::AuthEnc::GCM self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
    -        rv = gcm_add_iv(self, in_data, (unsigned long)in_data_len);
    +        rv = gcm_add_iv(&self->state, in_data, (unsigned long)in_data_len);
             if (rv != CRYPT_OK) croak("FATAL: gcm_add_iv failed: %s", error_to_string(rv));
             XPUSHs(ST(0)); /* return self */
         }
    @@ -114,8 +122,9 @@ adata_add(Crypt::AuthEnc::GCM self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
    -        rv = gcm_add_aad(self, in_data, (unsigned long)in_data_len);
    +        rv = gcm_add_aad(&self->state, in_data, (unsigned long)in_data_len);
             if (rv != CRYPT_OK) croak("FATAL: gcm_add_aad failed: %s", error_to_string(rv));
             XPUSHs(ST(0)); /* return self */
         }
    @@ -128,6 +137,7 @@ decrypt_add(Crypt::AuthEnc::GCM self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -137,10 +147,10 @@ decrypt_add(Crypt::AuthEnc::GCM self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = gcm_process(self, out_data, (unsigned long)in_data_len, in_data, GCM_DECRYPT);
    +          rv = gcm_process(&self->state, out_data, (unsigned long)in_data_len, in_data, GCM_DECRYPT);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
    -            croak("FATAL: encrypt_add/gcm_process failed: %s", error_to_string(rv));
    +            croak("FATAL: decrypt_add/gcm_process failed: %s", error_to_string(rv));
               }
             }
         }
    @@ -156,8 +166,10 @@ encrypt_done(Crypt::AuthEnc::GCM self)
             unsigned char tag[MAXBLOCKSIZE];
             unsigned long tag_len = sizeof(tag);
     
    -        rv = gcm_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = gcm_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: gcm_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
         }
     
    @@ -171,8 +183,10 @@ decrypt_done(Crypt::AuthEnc::GCM self, ...)
             STRLEN expected_tag_len;
             unsigned char *expected_tag;
     
    -        rv = gcm_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = gcm_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: gcm_done failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             if (items == 1) {
               XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
             }
    @@ -182,7 +196,7 @@ decrypt_done(Crypt::AuthEnc::GCM self, ...)
               if (expected_tag_len!=tag_len) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
    -          else if (memNE(expected_tag, tag, tag_len)) {
    +          else if (mem_neq(expected_tag, tag, tag_len)) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
               else {
    @@ -202,13 +216,19 @@ gcm_encrypt_authenticate(char *cipher_name, SV *key, SV *nonce, SV *header = NUL
             unsigned long tag_len = sizeof(tag);
             SV *output;
     
    -        if (SvPOK(key))       k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))     n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(plaintext)) pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    -        if (SvPOK(header))    h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(plaintext)) croak("FATAL: plaintext must be string/buffer scalar");
    +        pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, pt_len > 0 ? pt_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, pt_len);
    @@ -218,7 +238,7 @@ gcm_encrypt_authenticate(char *cipher_name, SV *key, SV *nonce, SV *header = NUL
     
             if (rv != CRYPT_OK) {
               SvREFCNT_dec(output);
    -          croak("FATAL: ccm_memory failed: %s", error_to_string(rv));
    +          croak("FATAL: gcm_memory failed: %s", error_to_string(rv));
             }
             XPUSHs(sv_2mortal(output));
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
    @@ -235,14 +255,21 @@ gcm_decrypt_verify(char *cipher_name, SV *key, SV *nonce, SV *header, SV *cipher
             unsigned long tag_len;
             SV *output;
     
    -        if (SvPOK(key))        k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))      n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(ciphertext)) ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    -        if (SvPOK(tagsv))      t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    -        if (SvPOK(header))     h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(ciphertext)) croak("FATAL: ciphertext must be string/buffer scalar");
    +        ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    +        if (!SvPOK_spec(tagsv)) croak("FATAL: tag must be string/buffer scalar");
    +        t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, ct_len > 0 ? ct_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, ct_len);
    
  • inc/CryptX_AuthEnc_OCB.xs.inc+56 30 modified
    @@ -18,13 +18,14 @@ new(Class, char * cipher_name, SV * key, SV * nonce, unsigned long taglen)
             n = (unsigned char *) SvPVbyte(nonce, n_len);
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if (id == -1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if (id == -1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
     
    -        Newz(0, RETVAL, 1, ocb3_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_ocb_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
     
    -        rv = ocb3_init(RETVAL, id, k, (unsigned long)k_len, n, (unsigned long)n_len, taglen);
    +        rv = ocb3_init(&RETVAL->state, id, k, (unsigned long)k_len, n, (unsigned long)n_len, taglen);
             if (rv != CRYPT_OK) {
    +          zeromem(RETVAL, sizeof(*RETVAL));
               Safefree(RETVAL);
               croak("FATAL: ocb setup failed: %s", error_to_string(rv));
             }
    @@ -35,14 +36,17 @@ new(Class, char * cipher_name, SV * key, SV * nonce, unsigned long taglen)
     void
     DESTROY(Crypt::AuthEnc::OCB self)
         CODE:
    -        Safefree(self);
    +        if (self) {
    +          zeromem(self, sizeof(*self));
    +          Safefree(self);
    +        }
     
     Crypt::AuthEnc::OCB
     clone(Crypt::AuthEnc::OCB self)
         CODE:
    -        Newz(0, RETVAL, 1, ocb3_state);
    +        Newz(0, RETVAL, 1, struct cryptx_authenc_ocb_struct);
             if (!RETVAL) croak("FATAL: Newz failed");
    -        Copy(self, RETVAL, 1, ocb3_state);
    +        Copy(self, RETVAL, 1, struct cryptx_authenc_ocb_struct);
         OUTPUT:
             RETVAL
     
    @@ -54,10 +58,11 @@ adata_add(Crypt::AuthEnc::OCB self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
     
             if (in_data_len>0) {
    -          rv = ocb3_add_aad(self, in_data, (unsigned long)in_data_len);
    +          rv = ocb3_add_aad(&self->state, in_data, (unsigned long)in_data_len);
               if (rv != CRYPT_OK) croak("FATAL: ocb3_add_aad failed: %s", error_to_string(rv));
             }
             XPUSHs(ST(0)); /* return self */
    @@ -71,6 +76,7 @@ encrypt_add(Crypt::AuthEnc::OCB self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -83,7 +89,7 @@ encrypt_add(Crypt::AuthEnc::OCB self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = ocb3_encrypt(self, in_data, (unsigned long)in_data_len, out_data);
    +          rv = ocb3_encrypt(&self->state, in_data, (unsigned long)in_data_len, out_data);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: ocb3_encrypt failed: %s", error_to_string(rv));
    @@ -101,9 +107,10 @@ encrypt_last(Crypt::AuthEnc::OCB self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
    -          rv = ocb3_encrypt_last(self, in_data, 0, NULL);
    +          rv = ocb3_encrypt_last(&self->state, in_data, 0, NULL);
               if (rv != CRYPT_OK) {
                 croak("FATAL: ocb3_encrypt_last failed: %s", error_to_string(rv));
               }
    @@ -114,7 +121,7 @@ encrypt_last(Crypt::AuthEnc::OCB self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = ocb3_encrypt_last(self, in_data, (unsigned long)in_data_len, out_data);
    +          rv = ocb3_encrypt_last(&self->state, in_data, (unsigned long)in_data_len, out_data);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: ocb3_encrypt_last failed: %s", error_to_string(rv));
    @@ -132,6 +139,7 @@ decrypt_add(Crypt::AuthEnc::OCB self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
               RETVAL = newSVpvn("", 0);
    @@ -144,7 +152,7 @@ decrypt_add(Crypt::AuthEnc::OCB self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = ocb3_decrypt(self, in_data, (unsigned long)in_data_len, out_data);
    +          rv = ocb3_decrypt(&self->state, in_data, (unsigned long)in_data_len, out_data);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
                 croak("FATAL: ocb3_decrypt failed: %s", error_to_string(rv));
    @@ -162,11 +170,12 @@ decrypt_last(Crypt::AuthEnc::OCB self, SV * data)
             STRLEN in_data_len;
             unsigned char *in_data, *out_data;
     
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
             in_data = (unsigned char *)SvPVbyte(data, in_data_len);
             if (in_data_len == 0) {
    -          rv = ocb3_decrypt_last(self, in_data, 0, NULL);
    +          rv = ocb3_decrypt_last(&self->state, in_data, 0, NULL);
               if (rv != CRYPT_OK) {
    -            croak("FATAL: ocb3_encrypt_last failed: %s", error_to_string(rv));
    +            croak("FATAL: ocb3_decrypt_last failed: %s", error_to_string(rv));
               }
               RETVAL = newSVpvn("", 0);
             }
    @@ -175,10 +184,10 @@ decrypt_last(Crypt::AuthEnc::OCB self, SV * data)
               SvPOK_only(RETVAL);
               SvCUR_set(RETVAL, in_data_len);
               out_data = (unsigned char *)SvPVX(RETVAL);
    -          rv = ocb3_decrypt_last(self, in_data, (unsigned long)in_data_len, out_data);
    +          rv = ocb3_decrypt_last(&self->state, in_data, (unsigned long)in_data_len, out_data);
               if (rv != CRYPT_OK) {
                 SvREFCNT_dec(RETVAL);
    -            croak("FATAL: ocb3_encrypt_last failed: %s", error_to_string(rv));
    +            croak("FATAL: ocb3_decrypt_last failed: %s", error_to_string(rv));
               }
             }
         }
    @@ -193,8 +202,10 @@ encrypt_done(Crypt::AuthEnc::OCB self)
             unsigned char tag[MAXBLOCKSIZE];
             unsigned long tag_len = sizeof(tag);
     
    -        rv = ocb3_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = ocb3_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: ocb3_done_encrypt failed: %s", error_to_string(rv));
    +        self->finalized = 1;
     
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
         }
    @@ -209,8 +220,10 @@ decrypt_done(Crypt::AuthEnc::OCB self, ...)
             STRLEN expected_tag_len;
             unsigned char *expected_tag;
     
    -        rv = ocb3_done(self, tag, &tag_len);
    +        if (self->finalized) croak("FATAL: AEAD object already finalized");
    +        rv = ocb3_done(&self->state, tag, &tag_len);
             if (rv != CRYPT_OK) croak("FATAL: ocb3_done_decrypt failed: %s", error_to_string(rv));
    +        self->finalized = 1;
             if (items == 1) {
               XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
             }
    @@ -220,7 +233,7 @@ decrypt_done(Crypt::AuthEnc::OCB self, ...)
               if (expected_tag_len!=tag_len) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
    -          else if (memNE(expected_tag, tag, tag_len)) {
    +          else if (mem_neq(expected_tag, tag, tag_len)) {
                 XPUSHs(sv_2mortal(newSViv(0))); /* false */
               }
               else {
    @@ -239,13 +252,19 @@ ocb_encrypt_authenticate(char *cipher_name, SV *key, SV *nonce, SV *header, unsi
             unsigned char tag[MAXBLOCKSIZE];
             SV *output;
     
    -        if (SvPOK(key))       k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))     n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(plaintext)) pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    -        if (SvPOK(header))    h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(plaintext)) croak("FATAL: plaintext must be string/buffer scalar");
    +        pt = (unsigned char *) SvPVbyte(plaintext, pt_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, pt_len > 0 ? pt_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, pt_len);
    @@ -257,7 +276,7 @@ ocb_encrypt_authenticate(char *cipher_name, SV *key, SV *nonce, SV *header, unsi
     
             if (rv != CRYPT_OK) {
               SvREFCNT_dec(output);
    -          croak("FATAL: ccm_memory failed: %s", error_to_string(rv));
    +          croak("FATAL: ocb3_encrypt_authenticate_memory failed: %s", error_to_string(rv));
             }
             XPUSHs(sv_2mortal(output));
             XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
    @@ -272,14 +291,21 @@ ocb_decrypt_verify(char *cipher_name, SV *key, SV *nonce, SV *header, SV *cipher
             int rv, id, stat = 0;
             SV *output;
     
    -        if (SvPOK(key))        k  = (unsigned char *) SvPVbyte(key, k_len);
    -        if (SvPOK(nonce))      n  = (unsigned char *) SvPVbyte(nonce, n_len);
    -        if (SvPOK(ciphertext)) ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    -        if (SvPOK(tagsv))      t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    -        if (SvPOK(header))     h  = (unsigned char *) SvPVbyte(header, h_len);
    +        if (!SvPOK_spec(key)) croak("FATAL: key must be string/buffer scalar");
    +        k  = (unsigned char *) SvPVbyte(key, k_len);
    +        if (!SvPOK_spec(nonce)) croak("FATAL: nonce must be string/buffer scalar");
    +        n  = (unsigned char *) SvPVbyte(nonce, n_len);
    +        if (!SvPOK_spec(ciphertext)) croak("FATAL: ciphertext must be string/buffer scalar");
    +        ct = (unsigned char *) SvPVbyte(ciphertext, ct_len);
    +        if (!SvPOK_spec(tagsv)) croak("FATAL: tag must be string/buffer scalar");
    +        t  = (unsigned char *) SvPVbyte(tagsv, t_len);
    +        if (header && SvOK(header)) {
    +          if (!SvPOK_spec(header)) croak("FATAL: header must be string/buffer scalar");
    +          h = (unsigned char *) SvPVbyte(header, h_len);
    +        }
     
             id = cryptx_internal_find_cipher(cipher_name);
    -        if(id==-1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
    +        if(id==-1) croak("FATAL: find_cipher failed for '%s'", cipher_name);
             output = NEWSV(0, ct_len > 0 ? ct_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, ct_len);
    
  • lib/Crypt/AuthEnc/CCM.pm+5 0 modified
    @@ -77,6 +77,9 @@ data in OO mode, pass C<''>.
     
     When verifying, C<decrypt_done($expected_tag)> is the safer form. The no-argument form of
     C<decrypt_done> only returns the computed tag.
    +The first C<encrypt_done> / C<decrypt_done> call finalizes the object. After that,
    +further C<encrypt_add>, C<decrypt_add>, C<encrypt_done>, and C<decrypt_done>
    +calls croak.
     
     =head1 EXPORT
     
    @@ -139,6 +142,7 @@ Returns a binary string of ciphertext (raw bytes).
     =head2 encrypt_done
     
     Returns the authentication tag as a binary string (raw bytes).
    +This call finalizes the current message.
     
      my $tag = $ae->encrypt_done;                  # returns $tag value
     
    @@ -151,6 +155,7 @@ Returns a binary string of plaintext (raw bytes).
     =head2 decrypt_done
     
     Without argument returns the computed tag as a binary string. With C<$tag> argument returns C<1> (success) or C<0> (failure).
    +This call finalizes the current message.
     
      my $tag = $ae->decrypt_done;           # returns $tag value
      #or
    
  • lib/Crypt/AuthEnc/ChaCha20Poly1305.pm+5 0 modified
    @@ -78,6 +78,9 @@ Use a fresh object per message. If you construct with C<new($key)> you must
     call C<set_iv($iv)> before adding AAD or processing plaintext/ciphertext.
     When verifying, C<decrypt_done($expected_tag)> is the safer one-step form;
     C<decrypt_done()> without arguments only returns the calculated tag.
    +The first C<encrypt_done> / C<decrypt_done> call finalizes the object. After that,
    +further C<set_iv>, C<set_iv_rfc7905>, C<adata_add>, C<encrypt_add>,
    +C<decrypt_add>, C<encrypt_done>, and C<decrypt_done> calls croak.
     
     =head1 EXPORT
     
    @@ -135,6 +138,7 @@ Returns a binary string of ciphertext (raw bytes).
     =head2 encrypt_done
     
     Returns the authentication tag as a binary string (raw bytes).
    +This call finalizes the current message.
     
      my $tag = $ae->encrypt_done();                 # returns $tag value
     
    @@ -147,6 +151,7 @@ Returns a binary string of plaintext (raw bytes).
     =head2 decrypt_done
     
     Without argument returns the computed tag as a binary string. With C<$tag> argument returns C<1> (success) or C<0> (failure).
    +This call finalizes the current message.
     
      my $tag = $ae->decrypt_done;           # returns $tag value
      #or
    
  • lib/Crypt/AuthEnc/EAX.pm+5 0 modified
    @@ -83,6 +83,9 @@ Use a fresh object per message. The optional C<$adata> argument to C<new> is
     equivalent to adding initial AAD before processing any payload. When verifying,
     C<decrypt_done($expected_tag)> is the safer one-step form; C<decrypt_done()>
     without arguments only returns the calculated tag.
    +The first C<encrypt_done> / C<decrypt_done> call finalizes the object. After that,
    +further C<adata_add>, C<encrypt_add>, C<decrypt_add>, C<encrypt_done>, and
    +C<decrypt_done> calls croak.
     
     =head1 EXPORT
     
    @@ -142,6 +145,7 @@ Returns a binary string of ciphertext (raw bytes).
     =head2 encrypt_done
     
     Returns the authentication tag as a binary string (raw bytes).
    +This call finalizes the current message.
     
      my $tag = $ae->encrypt_done();                 # returns $tag value
     
    @@ -154,6 +158,7 @@ Returns a binary string of plaintext (raw bytes).
     =head2 decrypt_done
     
     Without argument returns the computed tag as a binary string. With C<$tag> argument returns C<1> (success) or C<0> (failure).
    +This call finalizes the current message.
     
      my $tag = $ae->decrypt_done;           # returns $tag value
      #or
    
  • lib/Crypt/AuthEnc/GCM.pm+7 1 modified
    @@ -72,6 +72,9 @@ Galois/Counter Mode (GCM) - provides encryption and authentication.
     Use a fresh object per message unless you intentionally reuse the same key via C<reset>.
     The normal call order is: C<new>, one or more C<iv_add> calls, optional C<adata_add> calls,
     zero or more C<encrypt_add> / C<decrypt_add> calls, then C<encrypt_done> / C<decrypt_done>.
    +The first C<encrypt_done> / C<decrypt_done> call finalizes the object. After that,
    +further C<iv_add>, C<adata_add>, C<encrypt_add>, C<decrypt_add>, C<encrypt_done>,
    +and C<decrypt_done> calls croak until you call C<reset>.
     
     If you construct with C<new($cipher, $key)>, you must provide the IV via C<iv_add> before
     adding authenticated data or payload data. After C<reset>, start a new message with the same
    @@ -148,6 +151,7 @@ Returns a binary string of ciphertext (raw bytes).
     =head2 encrypt_done
     
     Returns the authentication tag as a binary string (raw bytes).
    +This call finalizes the current message.
     
      my $tag = $ae->encrypt_done();                # returns $tag value
     
    @@ -160,6 +164,7 @@ Returns a binary string of plaintext (raw bytes).
     =head2 decrypt_done
     
     Without argument returns the computed tag as a binary string. With C<$tag> argument returns C<1> (success) or C<0> (failure).
    +This call finalizes the current message.
     
      my $tag = $ae->decrypt_done;           # returns $tag value
      #or
    @@ -173,7 +178,8 @@ returns the computed tag.
      $ae->reset;
     
     Start a new message with the same key. After C<reset>, call C<iv_add> again, then
    -C<adata_add> if needed, before processing payload data.
    +C<adata_add> if needed, before processing payload data. C<reset> also clears the
    +finalized state set by C<encrypt_done> / C<decrypt_done>.
     
     =head2 clone
     
    
  • lib/Crypt/AuthEnc/OCB.pm+5 0 modified
    @@ -86,6 +86,9 @@ final partial block, then C<encrypt_done> or C<decrypt_done>.
     Use a fresh object per message. When verifying, C<decrypt_done($expected_tag)>
     is the safer one-step form; C<decrypt_done()> without arguments only returns
     the calculated tag.
    +The first C<encrypt_done> / C<decrypt_done> call finalizes the object. After that,
    +further C<adata_add>, C<encrypt_add>, C<encrypt_last>, C<decrypt_add>,
    +C<decrypt_last>, C<encrypt_done>, and C<decrypt_done> calls croak.
     
     =head1 EXPORT
     
    @@ -154,6 +157,7 @@ Returns a binary string of ciphertext (raw bytes).
     =head2 encrypt_done
     
     Returns the authentication tag as a binary string (raw bytes).
    +This call finalizes the current message.
     
      my $tag = $ae->encrypt_done();                 # returns $tag value
     
    @@ -172,6 +176,7 @@ Returns a binary string of plaintext (raw bytes).
     =head2 decrypt_done
     
     Without argument returns the computed tag as a binary string. With C<$tag> argument returns C<1> (success) or C<0> (failure).
    +This call finalizes the current message.
     
      my $tag = $ae->decrypt_done;           # returns $tag value
      #or
    
  • t/auth_enc_api.t+110 0 added
    @@ -0,0 +1,110 @@
    +use strict;
    +use warnings;
    +
    +use Test::More;
    +
    +use Crypt::AuthEnc::GCM qw(gcm_encrypt_authenticate);
    +use Crypt::AuthEnc::EAX;
    +use Crypt::AuthEnc::OCB;
    +use Crypt::AuthEnc::CCM;
    +use Crypt::AuthEnc::ChaCha20Poly1305 qw(chacha20poly1305_encrypt_authenticate);
    +
    +{
    +  package Local::OverStr;
    +  use overload q{""} => sub { $_[0]->{value} }, fallback => 1;
    +  sub new { bless { value => $_[1] }, $_[0] }
    +}
    +
    +sub dies_like {
    +  my ($code, $regex, $name) = @_;
    +  my $err = eval { $code->(); 1 } ? '' : $@;
    +  like($err, $regex, $name);
    +}
    +
    +my $aes_key   = "12345678901234561234567890123456";
    +my $chacha_key = $aes_key;
    +my $nonce12   = "123456789012";
    +my $ocb_pt    = "plain_half_12345";
    +my $short_pt  = "abc";
    +my $aad       = "adata-123456789012";
    +
    +{
    +  my $gcm = Crypt::AuthEnc::GCM->new("AES", $aes_key, $nonce12);
    +  $gcm->adata_add($aad);
    +  $gcm->encrypt_add($short_pt);
    +  my $gcm_clone = $gcm->clone;
    +  is($gcm->encrypt_done, $gcm_clone->encrypt_done, "GCM clone preserves tag state");
    +  dies_like(sub { $gcm->encrypt_done }, qr/AEAD object already finalized/, "GCM second encrypt_done croaks");
    +  dies_like(sub { $gcm->encrypt_add("x") }, qr/AEAD object already finalized/, "GCM add after encrypt_done croaks");
    +  $gcm->reset;
    +  $gcm->iv_add($nonce12);
    +  $gcm->adata_add($aad);
    +  $gcm->encrypt_add($short_pt);
    +  is(length($gcm->encrypt_done), 16, "GCM reset clears finalized state");
    +}
    +
    +{
    +  my $eax = Crypt::AuthEnc::EAX->new("AES", $aes_key, $nonce12);
    +  $eax->adata_add($aad);
    +  $eax->encrypt_add($short_pt);
    +  my $eax_clone = $eax->clone;
    +  is($eax->encrypt_done, $eax_clone->encrypt_done, "EAX clone preserves tag state");
    +  dies_like(sub { $eax->encrypt_done }, qr/AEAD object already finalized/, "EAX second encrypt_done croaks");
    +  dies_like(sub { $eax->encrypt_add("x") }, qr/AEAD object already finalized/, "EAX add after encrypt_done croaks");
    +}
    +
    +{
    +  my $ocb = Crypt::AuthEnc::OCB->new("AES", $aes_key, $nonce12, 16);
    +  $ocb->adata_add($aad);
    +  $ocb->encrypt_add($ocb_pt);
    +  my $ocb_clone = $ocb->clone;
    +  is($ocb->encrypt_done, $ocb_clone->encrypt_done, "OCB clone preserves tag state");
    +  dies_like(sub { $ocb->encrypt_done }, qr/AEAD object already finalized/, "OCB second encrypt_done croaks");
    +  dies_like(sub { $ocb->encrypt_add($ocb_pt) }, qr/AEAD object already finalized/, "OCB add after encrypt_done croaks");
    +}
    +
    +{
    +  my $ccm = Crypt::AuthEnc::CCM->new("AES", $aes_key, $nonce12, "", 16, length($short_pt));
    +  $ccm->encrypt_add($short_pt);
    +  my $ccm_clone = $ccm->clone;
    +  is($ccm->encrypt_done, $ccm_clone->encrypt_done, "CCM clone preserves tag state");
    +  dies_like(sub { $ccm->encrypt_done }, qr/AEAD object already finalized/, "CCM second encrypt_done croaks");
    +  dies_like(sub { $ccm->encrypt_add("x") }, qr/AEAD object already finalized/, "CCM add after encrypt_done croaks");
    +}
    +
    +{
    +  my $cp = Crypt::AuthEnc::ChaCha20Poly1305->new($chacha_key, $nonce12);
    +  $cp->adata_add($aad);
    +  $cp->encrypt_add($short_pt);
    +  my $cp_clone = $cp->clone;
    +  is($cp->encrypt_done, $cp_clone->encrypt_done, "ChaCha20Poly1305 clone preserves tag state");
    +  dies_like(sub { $cp->encrypt_done }, qr/AEAD object already finalized/, "ChaCha20Poly1305 second encrypt_done croaks");
    +  dies_like(sub { $cp->encrypt_add("x") }, qr/AEAD object already finalized/, "ChaCha20Poly1305 add after encrypt_done croaks");
    +}
    +
    +{
    +  my ($ct1, $tag1) = gcm_encrypt_authenticate('AES', $aes_key, $nonce12, '', $short_pt);
    +  my ($ct2, $tag2) = gcm_encrypt_authenticate(
    +    'AES',
    +    Local::OverStr->new($aes_key),
    +    Local::OverStr->new($nonce12),
    +    '',
    +    $short_pt,
    +  );
    +  is($ct2,  $ct1,  "GCM functional helper accepts overloaded key/nonce");
    +  is($tag2, $tag1, "GCM functional helper overloaded output matches plain scalars");
    +}
    +
    +{
    +  my ($ct1, $tag1) = chacha20poly1305_encrypt_authenticate($chacha_key, $nonce12, '', $short_pt);
    +  my ($ct2, $tag2) = chacha20poly1305_encrypt_authenticate(
    +    Local::OverStr->new($chacha_key),
    +    Local::OverStr->new($nonce12),
    +    '',
    +    $short_pt,
    +  );
    +  is($ct2,  $ct1,  "ChaCha20Poly1305 functional helper accepts overloaded key/nonce");
    +  is($tag2, $tag1, "ChaCha20Poly1305 functional helper overloaded output matches plain scalars");
    +}
    +
    +done_testing();
    
  • t/auth_enc_ccm.t+5 1 modified
    @@ -1,7 +1,7 @@
     use strict;
     use warnings;
     
    -use Test::More tests => 16;
    +use Test::More tests => 18;
     
     use Crypt::AuthEnc::CCM qw( ccm_encrypt_authenticate ccm_decrypt_verify );
     
    @@ -42,6 +42,8 @@ my $key   = "12345678901234561234567890123456";
       is(unpack('H*', $tag), "9485c6d5709b43431a4f05370cc22603", "ccm_encrypt_authenticate: tag");
       my $pt = ccm_decrypt_verify('AES', $key, $nonce, "header-abc", $ct, $tag);
       is($pt, "plain_halfplain_half", "ccm_decrypt_verify: plaintext");
    +  $pt = ccm_decrypt_verify('AES', $key, $nonce, "header-abc", $ct, $tag . ("A" x 200));
    +  is($pt, undef, "ccm_decrypt_verify: plaintext / oversized tag");
       substr($tag, 0, 1) = pack("H2", "AA");
       $pt = ccm_decrypt_verify('AES', $key, $nonce, "header-abc", $ct, $tag);
       is($pt, undef, "ccm_decrypt_verify: plaintext / bad tag");
    @@ -55,6 +57,8 @@ my $key   = "12345678901234561234567890123456";
       is(unpack('H*', $tag), "9e9cba5dd4939d0d8e2687c85c5d3b89", "ccm_encrypt_authenticate: tag (no header)");
       my $pt = ccm_decrypt_verify('AES', $key, $nonce, "", $ct, $tag);
       is($pt, "plain_halfplain_half", "ccm_decrypt_verify: plaintext (no header)");
    +  $pt = ccm_decrypt_verify('AES', $key, $nonce, "", $ct, $tag . ("A" x 200));
    +  is($pt, undef, "ccm_decrypt_verify: plaintext (no header) / oversized tag");
       substr($tag, 0, 1) = pack("H2", "AA");
       $pt = ccm_decrypt_verify('AES', $key, $nonce, "", $ct, $tag);
       is($pt, undef, "ccm_decrypt_verify: plaintext (no header) / bad tag");
    
  • t/auth_enc_chacha20poly1305.t+5 1 modified
    @@ -1,7 +1,7 @@
     use strict;
     use warnings;
     
    -use Test::More tests => 14;
    +use Test::More tests => 16;
     
     use Crypt::AuthEnc::ChaCha20Poly1305 qw( chacha20poly1305_encrypt_authenticate chacha20poly1305_decrypt_verify );
     
    @@ -44,6 +44,8 @@ my $key   = "12345678901234561234567890123456";
       is(unpack('H*', $tag), "d081beb3c3fe560c77f6c4e0da1d0dac", "chacha20poly1305_encrypt_authenticate: tag (no header)");
       my $pt = chacha20poly1305_decrypt_verify($key, "123456789012", "", $ct, $tag);
       is($pt, "plain_halfplain_half", "chacha20poly1305_decrypt_verify: plaintext (no header)");
    +  $pt = chacha20poly1305_decrypt_verify($key, "123456789012", "", $ct, $tag . ("A" x 200));
    +  is($pt, undef, "chacha20poly1305_decrypt_verify: plaintext (no header) / oversized tag");
       substr($tag, 0, 1) = pack("H2", "AA");
       $pt = chacha20poly1305_decrypt_verify($key, "123456789012", "", $ct, $tag);
       is($pt, undef, "chacha20poly1305_decrypt_verify: plaintext (no header) / bad tag");
    @@ -55,6 +57,8 @@ my $key   = "12345678901234561234567890123456";
       is(unpack('H*', $tag), "e6f20b492b7bf34c914c72717af6f232", "chacha20poly1305_encrypt_authenticate: tag (no header)");
       my $pt = chacha20poly1305_decrypt_verify($key, "123456789012", "adata-123456789012", $ct, $tag);
       is($pt, "plain_halfplain_half", "chacha20poly1305_decrypt_verify: plaintext (no header)");
    +  $pt = chacha20poly1305_decrypt_verify($key, "123456789012", "adata-123456789012", $ct, $tag . ("A" x 200));
    +  is($pt, undef, "chacha20poly1305_decrypt_verify: plaintext (no header) / oversized tag");
       substr($tag, 0, 1) = pack("H2", "AA");
       $pt = chacha20poly1305_decrypt_verify($key, "123456789012", "adata-123456789012", $ct, $tag);
       is($pt, undef, "chacha20poly1305_decrypt_verify: plaintext (no header) / bad tag");
    
  • t/auth_enc_eax.t+5 1 modified
    @@ -1,7 +1,7 @@
     use strict;
     use warnings;
     
    -use Test::More tests => 14;
    +use Test::More tests => 16;
     
     use Crypt::AuthEnc::EAX qw( eax_encrypt_authenticate eax_decrypt_verify );
     
    @@ -49,6 +49,8 @@ my $key   = "12345678901234561234567890123456";
       is(unpack('H*', $tag), "f83d77e5cf20979b3325266ff2fe342c", "eax_encrypt_authenticate: tag");
       my $pt = eax_decrypt_verify('AES', $key, $nonce, "abc", $ct, $tag);
       is($pt, "plain_halfplain_half", "eax_decrypt_verify: plaintext");
    +  $pt = eax_decrypt_verify('AES', $key, $nonce, "abc", $ct, $tag . ("A" x 200));
    +  is($pt, undef, "eax_decrypt_verify: plaintext / oversized tag");
       substr($tag, 0, 1) = pack("H2", "AA");
       $pt = eax_decrypt_verify('AES', $key, $nonce, "abc", $ct, $tag);
       is($pt, undef, "eax_decrypt_verify: plaintext / bad tag");
    @@ -60,6 +62,8 @@ my $key   = "12345678901234561234567890123456";
       is(unpack('H*', $tag), "e5ad22aa2ba3b30cd50eb59593364f1b", "eax_encrypt_authenticate: tag (no header)");
       my $pt = eax_decrypt_verify('AES', $key, $nonce, "", $ct, $tag);
       is($pt, "plain_halfplain_half", "eax_decrypt_verify: plaintext (no header)");
    +  $pt = eax_decrypt_verify('AES', $key, $nonce, "", $ct, $tag . ("A" x 200));
    +  is($pt, undef, "eax_decrypt_verify: plaintext (no header) / oversized tag");
       substr($tag, 0, 1) = pack("H2", "AA");
       $pt = eax_decrypt_verify('AES', $key, $nonce, "", $ct, $tag);
       is($pt, undef, "eax_decrypt_verify: plaintext (no header) / bad tag");
    
57e69e541b07

fix (maybe) for gcm crashes

https://github.com/DCIT/perl-CryptXKarel MikoApr 22, 2026via nvd-ref
2 files changed · +8 3
  • inc/CryptX_AuthEnc_GCM.xs.inc+5 2 modified
    @@ -246,8 +246,11 @@ gcm_decrypt_verify(char *cipher_name, SV *key, SV *nonce, SV *header, SV *cipher
             output = NEWSV(0, ct_len > 0 ? ct_len : 1); /* avoid zero! */
             SvPOK_only(output);
             SvCUR_set(output, ct_len);
    -        tag_len = (unsigned long)t_len;
    -        Copy(t, tag, t_len, unsigned char);
    +        /* Clamp malformed tags to the stack buffer size before copying. */
    +        tag_len = (unsigned long)(t_len > sizeof(tag) ? sizeof(tag) : t_len);
    +        if (tag_len > 0) {
    +          Copy(t, tag, tag_len, unsigned char);
    +        }
     
             rv = gcm_memory(id, k, (unsigned long)k_len, n, (unsigned long)n_len, h, (unsigned long)h_len,
                             (unsigned char *)SvPVX(output), (unsigned long)ct_len, ct, tag, &tag_len, GCM_DECRYPT);
    
  • t/auth_enc_gcm.t+3 1 modified
    @@ -1,7 +1,7 @@
     use strict;
     use warnings;
     
    -use Test::More tests => 14;
    +use Test::More tests => 15;
     
     use Crypt::AuthEnc::GCM qw( gcm_encrypt_authenticate gcm_decrypt_verify );
     
    @@ -47,6 +47,8 @@ my $key   = "12345678901234561234567890123456";
       is(unpack('H*', $tag), "1685ba0eda059ace4aab6539980c30c0", "gcm_encrypt_authenticate: tag (no header)");
       my $pt = gcm_decrypt_verify('AES', $key, "123456789012", "", $ct, $tag);
       is($pt, "plain_halfplain_half", "gcm_decrypt_verify: plaintext (no header)");
    +  $pt = gcm_decrypt_verify('AES', $key, "123456789012", "", $ct, $tag . ("A" x 200));
    +  is($pt, undef, "gcm_decrypt_verify: plaintext (no header) / oversized tag");
       substr($tag, 0, 1) = pack("H2", "AA");
       $pt = gcm_decrypt_verify('AES', $key, "123456789012", "", $ct, $tag);
       is($pt, undef, "gcm_decrypt_verify: plaintext (no header) / bad tag");
    

Vulnerability mechanics

Root cause

"Missing length validation on the caller-supplied authentication tag before copying it into a fixed 144-byte stack buffer in four AEAD decrypt_verify XS routines."

Attack vector

An attacker who controls the authentication-tag argument passed to one of the four affected `decrypt_verify` functions can supply a tag longer than 144 bytes. Because the XS code copies the tag into a fixed-size stack buffer without a length check, the excess bytes overwrite adjacent stack memory. The attacker must be able to call the Perl-level AEAD decrypt API (e.g. `Crypt::AuthEnc::GCM::decrypt_verify`) with an oversized tag; any application that forwards untrusted tag data to these helpers is exploitable.

Affected code

The four AEAD `decrypt_verify` XS routines — `gcm_decrypt_verify`, `ccm_decrypt_verify`, `chacha20poly1305_decrypt_verify`, and `eax_decrypt_verify` — each declare a fixed 144-byte stack buffer (`unsigned char tag[MAXBLOCKSIZE]`) and then copy the caller-supplied authentication tag into it via `Copy(t, tag, t_len, unsigned char)` without checking that `t_len` does not exceed `sizeof(tag)` [ref_id=1].

What the fix does

The patch adds a clamp before the `Copy` in each of the four `decrypt_verify` functions: `tag_len = (unsigned long)(t_len > sizeof(tag) ? sizeof(tag) : t_len);` and only copies when `tag_len > 0` [ref_id=1]. This ensures the stack buffer is never overrun. The same commit also introduces a `finalized` flag to prevent reuse of AEAD objects, adds input-type validation (`SvPOK_spec`), and clears sensitive memory on error paths, but the stack-buffer-overflow fix is the clamp on the tag length.

Preconditions

  • inputThe attacker must be able to supply the authentication-tag argument to one of the four affected decrypt_verify functions (gcm_decrypt_verify, ccm_decrypt_verify, chacha20poly1305_decrypt_verify, eax_decrypt_verify).
  • inputThe supplied tag must be longer than 144 bytes (MAXBLOCKSIZE).

Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.