Repository navigation
Heap out-of-bounds read and length underflow in der_decode_sequence_flexi on UTCTime/GeneralizedTime (develop branch) #812
Description
Description
A heap out-of-bounds read and buffer length underflow vulnerability exists in der_decode_sequence_flexi() when decoding ASN.1 sequences containing UTCTime (tag 0x17) or GeneralizedTime (tag 0x18).
The bug is reachable via public APIs such as ecc_import_pkcs8 and x509_decode_spki.
Version
develop branch, commit 6c6d510 (and post-v1.18.2)
Additional Information
Root cause
In src/pk/asn1/der/sequence/der_decode_sequence_flexi.c:380-388:
case 0x17: { /* UTCTime */
len = *inlen;
if ((err = der_decode_utctime(in, &len, l->data)) != CRYPT_OK) {
goto error;
}
/* FLAW: len (which tracks consumed TLV bytes) is overwritten by re-encode length */
if ((err = der_length_utctime(l->data, &len)) != CRYPT_OK) {
goto error;
}
...
break;
}And in the loop footer (lines 509-512):
totlen += len;
in += len;
*inlen -= len;der_length_utctime() calculates the length needed to re-encode the canonical structure, which does not necessarily match the actual TLV bytes consumed. For non-canonical inputs (e.g. 17-byte input yielding re-encode length of 19 bytes), len becomes larger than the consumed bytes:
in += lenadvances past the actual end of the input buffer.*inlen -= lenunderflows the unsigned counter to~2^64.- The parser continues reading subsequent ASN.1 tags out of bounds from heap memory and still returns
CRYPT_OK.
The same pattern exists for 0x18 (GeneralizedTime).
Suggested fix
Do not overwrite len with the re-encode length calculation, and ensure safe bounds in the loop footer:
--- a/src/pk/asn1/der/sequence/der_decode_sequence_flexi.c
+++ b/src/pk/asn1/der/sequence/der_decode_sequence_flexi.c
@@ -380,8 +380,8 @@ int s_der_decode_sequence_flexi(const unsigned char *in, unsigned long *inlen,
case 0x17: {
len = *inlen;
if ((err = der_decode_utctime(in, &len, l->data)) != CRYPT_OK) {
goto error;
}
- if ((err = der_length_utctime(l->data, &len)) != CRYPT_OK) {
- goto error;
- }
break;
}
@@ -391,8 +391,8 @@ int s_der_decode_sequence_flexi(const unsigned char *in, unsigned long *inlen,
case 0x18: {
len = *inlen;
if ((err = der_decode_generalizedtime(in, &len, l->data)) != CRYPT_OK) {
goto error;
}
- if ((err = der_length_generalizedtime(l->data, &len)) != CRYPT_OK) {
- goto error;
- }
break;
}
@@ -509,6 +503,10 @@ int s_der_decode_sequence_flexi(const unsigned char *in, unsigned long *inlen,
+ if (len > *inlen) {
+ err = CRYPT_INVALID_PACKET;
+ goto error;
+ }
totlen += len;
in += len;
*inlen -= len;Reproduction
Feeding a 45-byte PKCS#8 private key containing an inner OCTET STRING holding non-canonical UTCTime bytes 17 0f "0000000000+0400" to ecc_import_pkcs8() triggers the bug:
#include <tomcrypt.h>
int main(void) {
static const unsigned char p8[45] = {
0x30, 0x2b, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06,
0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03,
0x01, 0x07, 0x04, 0x11, 0x17, 0x0f, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x2b, 0x30, 0x34, 0x30, 0x30
};
ecc_key key;
register_cipher(&aes_desc);
return ecc_import_pkcs8(p8, sizeof(p8), NULL, 0, &key);
}Call stacks (AddressSanitizer)
==ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 1 at 0x5030000000b3
#0 in der_decode_asn1_identifier src/pk/asn1/der/general/der_decode_asn1_identifier.c:74:17
#1 in s_der_decode_sequence_flexi src/pk/asn1/der/sequence/der_decode_sequence_flexi.c:84:18
#2 in ecc_import_pkcs8_asn1 src/pk/ecc/ecc_import_pkcs8.c:102:8
#3 in ecc_import_pkcs8 src/pk/ecc/ecc_import_pkcs8.c:160:10
0x5030000000b3 is located 0 bytes after 19-byte region [0x5030000000a0,0x5030000000b3)
allocated by thread T0 here:
#1 in s_der_decode_sequence_flexi der_decode_sequence_flexi.c:211:28
#2 in pkcs8_decode_flexi src/pk/asn1/pkcs8/pkcs8_decode_flexi.c:34
Please let me know if any further details are required. Thank you!
Activity