From 2ac4c6f7b2b2af20c0e2b0ba05367e454cd11b33 Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Thu, 22 Mar 2018 09:39:53 +0000 Subject: [PATCH] Limit ASN.1 constructed types recursive definition depth Constructed types with a recursive definition (such as can be found in PKCS7) could eventually exceed the stack given malicious input with excessive recursion. Therefore we limit the stack depth. CVE-2018-0739 Credit to OSSFuzz for finding this issue. Reviewed-by: Rich Salz --- crypto/asn1/asn1_err.c | 3 ++- crypto/asn1/tasn_dec.c | 47 ++++++++++++++++++++++++++++-------------- include/openssl/asn1.h | 1 + 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/crypto/asn1/asn1_err.c b/crypto/asn1/asn1_err.c index dd0e99e624..8602c408d9 100644 --- a/crypto/asn1/asn1_err.c +++ b/crypto/asn1/asn1_err.c @@ -1,6 +1,6 @@ /* * Generated by util/mkerr.pl DO NOT EDIT - * Copyright 1995-2017 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the OpenSSL license (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -203,6 +203,7 @@ static ERR_STRING_DATA ASN1_str_reasons[] = { {ERR_REASON(ASN1_R_MSTRING_NOT_UNIVERSAL), "mstring not universal"}, {ERR_REASON(ASN1_R_MSTRING_WRONG_TAG), "mstring wrong tag"}, {ERR_REASON(ASN1_R_NESTED_ASN1_STRING), "nested asn1 string"}, + {ERR_REASON(ASN1_R_NESTED_TOO_DEEP), "nested too deep"}, {ERR_REASON(ASN1_R_NON_HEX_CHARACTERS), "non hex characters"}, {ERR_REASON(ASN1_R_NOT_ASCII_FORMAT), "not ascii format"}, {ERR_REASON(ASN1_R_NOT_ENOUGH_DATA), "not enough data"}, diff --git a/crypto/asn1/tasn_dec.c b/crypto/asn1/tasn_dec.c index c9b637516e..bc3d193757 100644 --- a/crypto/asn1/tasn_dec.c +++ b/crypto/asn1/tasn_dec.c @@ -17,9 +17,18 @@ #include "internal/numbers.h" #include "asn1_locl.h" +/* + * Constructed types with a recursive definition (such as can be found in PKCS7) + * could eventually exceed the stack given malicious input with excessive + * recursion. Therefore we limit the stack depth. This is the maximum number of + * recursive invocations of asn1_item_embed_d2i(). + */ +#define ASN1_MAX_CONSTRUCTED_NEST 30 + static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, const ASN1_ITEM *it, - int tag, int aclass, char opt, ASN1_TLC *ctx); + int tag, int aclass, char opt, ASN1_TLC *ctx, + int depth); static int asn1_check_eoc(const unsigned char **in, long len); static int asn1_find_end(const unsigned char **in, long len, char inf); @@ -37,11 +46,11 @@ static int asn1_check_tlen(long *olen, int *otag, unsigned char *oclass, static int asn1_template_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, const ASN1_TEMPLATE *tt, char opt, - ASN1_TLC *ctx); + ASN1_TLC *ctx, int depth); static int asn1_template_noexp_d2i(ASN1_VALUE **val, const unsigned char **in, long len, const ASN1_TEMPLATE *tt, char opt, - ASN1_TLC *ctx); + ASN1_TLC *ctx, int depth); static int asn1_d2i_ex_primitive(ASN1_VALUE **pval, const unsigned char **in, long len, const ASN1_ITEM *it, @@ -111,7 +120,7 @@ int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, int tag, int aclass, char opt, ASN1_TLC *ctx) { int rv; - rv = asn1_item_embed_d2i(pval, in, len, it, tag, aclass, opt, ctx); + rv = asn1_item_embed_d2i(pval, in, len, it, tag, aclass, opt, ctx, 0); if (rv <= 0) ASN1_item_ex_free(pval, it); return rv; @@ -124,7 +133,8 @@ int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, const ASN1_ITEM *it, - int tag, int aclass, char opt, ASN1_TLC *ctx) + int tag, int aclass, char opt, ASN1_TLC *ctx, + int depth) { const ASN1_TEMPLATE *tt, *errtt = NULL; const ASN1_EXTERN_FUNCS *ef; @@ -145,6 +155,11 @@ static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in, else asn1_cb = 0; + if (++depth > ASN1_MAX_CONSTRUCTED_NEST) { + ASN1err(ASN1_F_ASN1_ITEM_EMBED_D2I, ASN1_R_NESTED_TOO_DEEP); + goto err; + } + switch (it->itype) { case ASN1_ITYPE_PRIMITIVE: if (it->templates) { @@ -160,7 +175,7 @@ static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in, goto err; } return asn1_template_ex_d2i(pval, in, len, - it->templates, opt, ctx); + it->templates, opt, ctx, depth); } return asn1_d2i_ex_primitive(pval, in, len, it, tag, aclass, opt, ctx); @@ -221,7 +236,7 @@ static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in, /* * We mark field as OPTIONAL so its absence can be recognised. */ - ret = asn1_template_ex_d2i(pchptr, &p, len, tt, 1, ctx); + ret = asn1_template_ex_d2i(pchptr, &p, len, tt, 1, ctx, depth); /* If field not present, try the next one */ if (ret == -1) continue; @@ -344,7 +359,8 @@ static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in, * attempt to read in field, allowing each to be OPTIONAL */ - ret = asn1_template_ex_d2i(pseqval, &p, len, seqtt, isopt, ctx); + ret = asn1_template_ex_d2i(pseqval, &p, len, seqtt, isopt, ctx, + depth); if (!ret) { errtt = seqtt; goto err; @@ -420,7 +436,7 @@ static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in, static int asn1_template_ex_d2i(ASN1_VALUE **val, const unsigned char **in, long inlen, const ASN1_TEMPLATE *tt, char opt, - ASN1_TLC *ctx) + ASN1_TLC *ctx, int depth) { int flags, aclass; int ret; @@ -455,7 +471,7 @@ static int asn1_template_ex_d2i(ASN1_VALUE **val, return 0; } /* We've found the field so it can't be OPTIONAL now */ - ret = asn1_template_noexp_d2i(val, &p, len, tt, 0, ctx); + ret = asn1_template_noexp_d2i(val, &p, len, tt, 0, ctx, depth); if (!ret) { ASN1err(ASN1_F_ASN1_TEMPLATE_EX_D2I, ERR_R_NESTED_ASN1_ERROR); return 0; @@ -479,7 +495,7 @@ static int asn1_template_ex_d2i(ASN1_VALUE **val, } } } else - return asn1_template_noexp_d2i(val, in, inlen, tt, opt, ctx); + return asn1_template_noexp_d2i(val, in, inlen, tt, opt, ctx, depth); *in = p; return 1; @@ -491,7 +507,7 @@ static int asn1_template_ex_d2i(ASN1_VALUE **val, static int asn1_template_noexp_d2i(ASN1_VALUE **val, const unsigned char **in, long len, const ASN1_TEMPLATE *tt, char opt, - ASN1_TLC *ctx) + ASN1_TLC *ctx, int depth) { int flags, aclass; int ret; @@ -573,7 +589,8 @@ static int asn1_template_noexp_d2i(ASN1_VALUE **val, } skfield = NULL; if (!asn1_item_embed_d2i(&skfield, &p, len, - ASN1_ITEM_ptr(tt->item), -1, 0, 0, ctx)) { + ASN1_ITEM_ptr(tt->item), -1, 0, 0, ctx, + depth)) { ASN1err(ASN1_F_ASN1_TEMPLATE_NOEXP_D2I, ERR_R_NESTED_ASN1_ERROR); /* |skfield| may be partially allocated despite failure. */ @@ -595,7 +612,7 @@ static int asn1_template_noexp_d2i(ASN1_VALUE **val, /* IMPLICIT tagging */ ret = asn1_item_embed_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), tt->tag, aclass, opt, - ctx); + ctx, depth); if (!ret) { ASN1err(ASN1_F_ASN1_TEMPLATE_NOEXP_D2I, ERR_R_NESTED_ASN1_ERROR); goto err; @@ -604,7 +621,7 @@ static int asn1_template_noexp_d2i(ASN1_VALUE **val, } else { /* Nothing special */ ret = asn1_item_embed_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), - -1, 0, opt, ctx); + -1, 0, opt, ctx, depth); if (!ret) { ASN1err(ASN1_F_ASN1_TEMPLATE_NOEXP_D2I, ERR_R_NESTED_ASN1_ERROR); goto err; diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h index 35637ccbdb..7e8788ff98 100644 --- a/include/openssl/asn1.h +++ b/include/openssl/asn1.h @@ -1051,6 +1051,7 @@ int ERR_load_ASN1_strings(void); # define ASN1_R_MSTRING_NOT_UNIVERSAL 139 # define ASN1_R_MSTRING_WRONG_TAG 140 # define ASN1_R_NESTED_ASN1_STRING 197 +# define ASN1_R_NESTED_TOO_DEEP 201 # define ASN1_R_NON_HEX_CHARACTERS 141 # define ASN1_R_NOT_ASCII_FORMAT 190 # define ASN1_R_NOT_ENOUGH_DATA 142 -- 2.34.1