Don't use object reuse in X509_parse_from_buffer.
Instead, move the CRYPTO_BUFFER into ASN1_ENCODING (due to padding,
upgrading the two bitfields do a pointer doesn't increase memory usage),
and instead thread a CRYPTO_BUFFER parameter through tasn_dec.c.
Later, I want to reimplement the X509 and X509_CINF parsers with CBS/CBB
directly (https://crbug.com/boringssl/547), but that will be easier once
the whole crypto/asn1 machinery is rewritten with CBS/CBB
(https://crbug.com/boringssl/548). That, in turn, will be easier with
object reuse gone. But to get rid of object reuse, I need to remove the
one place in the library where we ourselves use it.
Bug: 550
Change-Id: Ia4df3da9280f808b124ac1f4ad58745dfe0f49e2
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/56646
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/asn1/internal.h b/crypto/asn1/internal.h
index 49286ea..a60a037 100644
--- a/crypto/asn1/internal.h
+++ b/crypto/asn1/internal.h
@@ -122,20 +122,16 @@
ASN1_OBJECT *ASN1_OBJECT_new(void);
-// ASN1_ENCODING structure: this is used to save the received
-// encoding of an ASN1 type. This is useful to get round
-// problems with invalid encodings which can break signatures.
+// ASN1_ENCODING is used to save the received encoding of an ASN.1 type. This
+// avoids problems with invalid encodings that break signatures.
typedef struct ASN1_ENCODING_st {
- unsigned char *enc; // DER encoding
- long len; // Length of encoding, or zero if not present.
- // alias_only is zero if |enc| owns the buffer that it points to
- // (although |enc| may still be NULL). If one, |enc| points into a
- // buffer that is owned elsewhere.
- unsigned alias_only : 1;
- // alias_only_on_next_parse is one iff the next parsing operation
- // should avoid taking a copy of the input and rather set
- // |alias_only|.
- unsigned alias_only_on_next_parse : 1;
+ // enc is the saved DER encoding. Its ownership is determined by |buf|.
+ uint8_t *enc;
+ // len is the length of |enc|. If zero, there is no saved encoding.
+ size_t len;
+ // buf, if non-NULL, is the |CRYPTO_BUFFER| that |enc| points into. If NULL,
+ // |enc| must be released with |OPENSSL_free|.
+ CRYPTO_BUFFER *buf;
} ASN1_ENCODING;
OPENSSL_EXPORT int asn1_utctime_to_tm(struct tm *tm, const ASN1_UTCTIME *d,
@@ -147,9 +143,18 @@
void ASN1_item_ex_free(ASN1_VALUE **pval, const ASN1_ITEM *it);
void ASN1_template_free(ASN1_VALUE **pval, const ASN1_TEMPLATE *tt);
+
+// ASN1_item_ex_d2i parses |len| bytes from |*in| as a structure of type |it|
+// and writes the result to |*pval|. If |tag| is non-negative, |it| is
+// implicitly tagged with the tag specified by |tag| and |aclass|. If |opt| is
+// non-zero, the value is optional. If |buf| is non-NULL, |*in| must point into
+// |buf|.
+//
+// This function returns one and advances |*in| if an object was successfully
+// parsed, -1 if an optional value was successfully skipped, and zero on error.
int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len,
const ASN1_ITEM *it, int tag, int aclass, char opt,
- ASN1_TLC *ctx);
+ CRYPTO_BUFFER *buf);
// ASN1_item_ex_i2d encodes |*pval| as a value of type |it| to |out| under the
// i2d output convention. It returns a non-zero length on success and -1 on
@@ -191,8 +196,11 @@
int asn1_enc_restore(int *len, unsigned char **out, ASN1_VALUE **pval,
const ASN1_ITEM *it);
-int asn1_enc_save(ASN1_VALUE **pval, const unsigned char *in, int inlen,
- const ASN1_ITEM *it);
+// asn1_enc_save saves |inlen| bytes from |in| as |*pval|'s saved encoding. It
+// returns one on success and zero on error. If |buf| is non-NULL, |in| must
+// point into |buf|.
+int asn1_enc_save(ASN1_VALUE **pval, const uint8_t *in, size_t inlen,
+ const ASN1_ITEM *it, CRYPTO_BUFFER *buf);
// asn1_encoding_clear clears the cached encoding in |enc|.
void asn1_encoding_clear(ASN1_ENCODING *enc);
diff --git a/crypto/asn1/tasn_dec.c b/crypto/asn1/tasn_dec.c
index 0fb7344..bdde8e3 100644
--- a/crypto/asn1/tasn_dec.c
+++ b/crypto/asn1/tasn_dec.c
@@ -59,6 +59,7 @@
#include <openssl/bytestring.h>
#include <openssl/err.h>
#include <openssl/mem.h>
+#include <openssl/pool.h>
#include <limits.h>
#include <string.h>
@@ -79,10 +80,10 @@
static int asn1_template_ex_d2i(ASN1_VALUE **pval, const unsigned char **in,
long len, const ASN1_TEMPLATE *tt, char opt,
- int depth);
+ CRYPTO_BUFFER *buf, int depth);
static int asn1_template_noexp_d2i(ASN1_VALUE **val, const unsigned char **in,
long len, const ASN1_TEMPLATE *tt, char opt,
- int depth);
+ CRYPTO_BUFFER *buf, int depth);
static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len,
int utype, const ASN1_ITEM *it);
static int asn1_d2i_ex_primitive(ASN1_VALUE **pval, const unsigned char **in,
@@ -90,7 +91,7 @@
int aclass, char opt);
static int asn1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in,
long len, const ASN1_ITEM *it, int tag, int aclass,
- char opt, int depth);
+ char opt, CRYPTO_BUFFER *buf, int depth);
// Table to convert tags to bit values, used for MSTRING type
static const unsigned long tag2bit[31] = {
@@ -148,7 +149,8 @@
pval = &ptmpval;
}
- if (asn1_item_ex_d2i(pval, in, len, it, -1, 0, 0, 0) > 0) {
+ if (asn1_item_ex_d2i(pval, in, len, it, /*tag=*/-1, /*aclass=*/0, /*opt=*/0,
+ /*buf=*/NULL, /*depth=*/0) > 0) {
return *pval;
}
return NULL;
@@ -159,7 +161,7 @@
static int asn1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in,
long len, const ASN1_ITEM *it, int tag, int aclass,
- char opt, int depth) {
+ char opt, CRYPTO_BUFFER *buf, int depth) {
const ASN1_TEMPLATE *tt, *errtt = NULL;
const unsigned char *p = NULL, *q;
unsigned char oclass;
@@ -172,6 +174,11 @@
return 0;
}
+ if (buf != NULL) {
+ assert(CRYPTO_BUFFER_data(buf) <= *in &&
+ *in + len <= CRYPTO_BUFFER_data(buf) + CRYPTO_BUFFER_len(buf));
+ }
+
// Bound |len| to comfortably fit in an int. Lengths in this module often
// switch between int and long without overflow checks.
if (len > INT_MAX / 2) {
@@ -194,7 +201,8 @@
OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE);
goto err;
}
- return asn1_template_ex_d2i(pval, in, len, it->templates, opt, depth);
+ return asn1_template_ex_d2i(pval, in, len, it->templates, opt, buf,
+ depth);
}
return asn1_d2i_ex_primitive(pval, in, len, it, tag, aclass, opt);
break;
@@ -277,7 +285,7 @@
for (i = 0, tt = it->templates; i < it->tcount; i++, tt++) {
pchptr = asn1_get_field_ptr(pval, tt);
// We mark field as OPTIONAL so its absence can be recognised.
- ret = asn1_template_ex_d2i(pchptr, &p, len, tt, 1, depth);
+ ret = asn1_template_ex_d2i(pchptr, &p, len, tt, 1, buf, depth);
// If field not present, try the next one
if (ret == -1) {
continue;
@@ -383,7 +391,7 @@
}
// attempt to read in field, allowing each to be OPTIONAL
- ret = asn1_template_ex_d2i(pseqval, &p, len, seqtt, isopt, depth);
+ ret = asn1_template_ex_d2i(pseqval, &p, len, seqtt, isopt, buf, depth);
if (!ret) {
errtt = seqtt;
goto err;
@@ -422,7 +430,7 @@
}
}
// Save encoding
- if (!asn1_enc_save(pval, *in, p - *in, it)) {
+ if (!asn1_enc_save(pval, *in, p - *in, it, buf)) {
goto auxerr;
}
if (asn1_cb && !asn1_cb(ASN1_OP_D2I_POST, pval, it, NULL)) {
@@ -449,8 +457,9 @@
int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len,
const ASN1_ITEM *it, int tag, int aclass, char opt,
- ASN1_TLC *ctx) {
- return asn1_item_ex_d2i(pval, in, len, it, tag, aclass, opt, 0);
+ CRYPTO_BUFFER *buf) {
+ return asn1_item_ex_d2i(pval, in, len, it, tag, aclass, opt, buf,
+ /*depth=*/0);
}
// Templates are handled with two separate functions. One handles any
@@ -458,7 +467,7 @@
static int asn1_template_ex_d2i(ASN1_VALUE **val, const unsigned char **in,
long inlen, const ASN1_TEMPLATE *tt, char opt,
- int depth) {
+ CRYPTO_BUFFER *buf, int depth) {
int aclass;
int ret;
long len;
@@ -490,7 +499,7 @@
return 0;
}
// We've found the field so it can't be OPTIONAL now
- ret = asn1_template_noexp_d2i(val, &p, len, tt, 0, depth);
+ ret = asn1_template_noexp_d2i(val, &p, len, tt, /*opt=*/0, buf, depth);
if (!ret) {
OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
return 0;
@@ -503,7 +512,7 @@
goto err;
}
} else {
- return asn1_template_noexp_d2i(val, in, inlen, tt, opt, depth);
+ return asn1_template_noexp_d2i(val, in, inlen, tt, opt, buf, depth);
}
*in = p;
@@ -516,7 +525,7 @@
static int asn1_template_noexp_d2i(ASN1_VALUE **val, const unsigned char **in,
long len, const ASN1_TEMPLATE *tt, char opt,
- int depth) {
+ CRYPTO_BUFFER *buf, int depth) {
int aclass;
int ret;
const unsigned char *p;
@@ -574,8 +583,8 @@
ASN1_VALUE *skfield;
const unsigned char *q = p;
skfield = NULL;
- if (!asn1_item_ex_d2i(&skfield, &p, len, ASN1_ITEM_ptr(tt->item), -1, 0,
- 0, depth)) {
+ if (!asn1_item_ex_d2i(&skfield, &p, len, ASN1_ITEM_ptr(tt->item),
+ /*tag=*/-1, /*aclass=*/0, /*opt=*/0, buf, depth)) {
OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
goto err;
}
@@ -589,7 +598,7 @@
} else if (flags & ASN1_TFLG_IMPTAG) {
// IMPLICIT tagging
ret = asn1_item_ex_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), tt->tag,
- aclass, opt, depth);
+ aclass, opt, buf, depth);
if (!ret) {
OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
goto err;
@@ -598,8 +607,8 @@
}
} else {
// Nothing special
- ret = asn1_item_ex_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), -1, 0, opt,
- depth);
+ ret = asn1_item_ex_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), /*tag=*/-1,
+ /*aclass=*/0, opt, buf, depth);
if (!ret) {
OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
goto err;
diff --git a/crypto/asn1/tasn_utl.c b/crypto/asn1/tasn_utl.c
index 741ce17..488c206 100644
--- a/crypto/asn1/tasn_utl.c
+++ b/crypto/asn1/tasn_utl.c
@@ -63,6 +63,7 @@
#include <openssl/err.h>
#include <openssl/mem.h>
#include <openssl/obj.h>
+#include <openssl/pool.h>
#include <openssl/thread.h>
#include "../internal.h"
@@ -135,8 +136,7 @@
if (enc) {
enc->enc = NULL;
enc->len = 0;
- enc->alias_only = 0;
- enc->alias_only_on_next_parse = 0;
+ enc->buf = NULL;
}
}
@@ -147,42 +147,41 @@
}
}
-int asn1_enc_save(ASN1_VALUE **pval, const unsigned char *in, int inlen,
- const ASN1_ITEM *it) {
+int asn1_enc_save(ASN1_VALUE **pval, const uint8_t *in, size_t in_len,
+ const ASN1_ITEM *it, CRYPTO_BUFFER *buf) {
ASN1_ENCODING *enc;
enc = asn1_get_enc_ptr(pval, it);
if (!enc) {
return 1;
}
- if (!enc->alias_only) {
- OPENSSL_free(enc->enc);
- }
-
- enc->alias_only = enc->alias_only_on_next_parse;
- enc->alias_only_on_next_parse = 0;
-
- if (enc->alias_only) {
+ asn1_encoding_clear(enc);
+ if (buf != NULL) {
+ assert(CRYPTO_BUFFER_data(buf) <= in &&
+ in + in_len <= CRYPTO_BUFFER_data(buf) + CRYPTO_BUFFER_len(buf));
+ CRYPTO_BUFFER_up_ref(buf);
+ enc->buf = buf;
enc->enc = (uint8_t *)in;
} else {
- enc->enc = OPENSSL_memdup(in, inlen);
+ enc->enc = OPENSSL_memdup(in, in_len);
if (!enc->enc) {
return 0;
}
}
- enc->len = inlen;
+ enc->len = in_len;
return 1;
}
void asn1_encoding_clear(ASN1_ENCODING *enc) {
- if (!enc->alias_only) {
+ if (enc->buf != NULL) {
+ CRYPTO_BUFFER_free(enc->buf);
+ } else {
OPENSSL_free(enc->enc);
}
enc->enc = NULL;
enc->len = 0;
- enc->alias_only = 0;
- enc->alias_only_on_next_parse = 0;
+ enc->buf = NULL;
}
int asn1_enc_restore(int *len, unsigned char **out, ASN1_VALUE **pval,
diff --git a/crypto/x509/internal.h b/crypto/x509/internal.h
index 7bf14c9..6c594ec 100644
--- a/crypto/x509/internal.h
+++ b/crypto/x509/internal.h
@@ -159,7 +159,6 @@
NAME_CONSTRAINTS *nc;
unsigned char cert_hash[SHA256_DIGEST_LENGTH];
X509_CERT_AUX *aux;
- CRYPTO_BUFFER *buf;
CRYPTO_MUTEX lock;
} /* X509 */;
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 2e885c4..178e5f8 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -2381,12 +2381,18 @@
size_t data2_len;
bssl::UniquePtr<uint8_t> data2;
ASSERT_TRUE(PEMToDER(&data2, &data2_len, kLeafPEM));
+ EXPECT_TRUE(buffers_alias(root->cert_info->enc.enc, root->cert_info->enc.len,
+ CRYPTO_BUFFER_data(buf.get()),
+ CRYPTO_BUFFER_len(buf.get())));
X509 *x509p = root.get();
const uint8_t *inp = data2.get();
X509 *ret = d2i_X509(&x509p, &inp, data2_len);
ASSERT_EQ(root.get(), ret);
- ASSERT_EQ(nullptr, root->buf);
+ ASSERT_EQ(nullptr, root->cert_info->enc.buf);
+ EXPECT_FALSE(buffers_alias(root->cert_info->enc.enc, root->cert_info->enc.len,
+ CRYPTO_BUFFER_data(buf.get()),
+ CRYPTO_BUFFER_len(buf.get())));
// Free |data2| and ensure that |root| took its own copy. Otherwise the
// following will trigger a use-after-free.
@@ -2401,7 +2407,7 @@
ASSERT_EQ(static_cast<long>(data2_len), i2d_len);
ASSERT_EQ(0, OPENSSL_memcmp(data2.get(), i2d, i2d_len));
- ASSERT_EQ(nullptr, root->buf);
+ ASSERT_EQ(nullptr, root->cert_info->enc.buf);
}
TEST(X509Test, TestFailedParseFromBuffer) {
diff --git a/crypto/x509/x_name.c b/crypto/x509/x_name.c
index 1d6691b..f017423 100644
--- a/crypto/x509/x_name.c
+++ b/crypto/x509/x_name.c
@@ -206,7 +206,7 @@
ASN1_VALUE *intname_val = NULL;
ret = ASN1_item_ex_d2i(&intname_val, &p, len,
ASN1_ITEM_rptr(X509_NAME_INTERNAL), /*tag=*/-1,
- /*aclass=*/0, opt, ctx);
+ /*aclass=*/0, opt, /*buf=*/NULL);
if (ret <= 0) {
return ret;
}
diff --git a/crypto/x509/x_x509.c b/crypto/x509/x_x509.c
index 4f23fd6..5205267 100644
--- a/crypto/x509/x_x509.c
+++ b/crypto/x509/x_x509.c
@@ -101,16 +101,10 @@
ret->akid = NULL;
ret->aux = NULL;
ret->crldp = NULL;
- ret->buf = NULL;
CRYPTO_new_ex_data(&ret->ex_data);
CRYPTO_MUTEX_init(&ret->lock);
break;
- case ASN1_OP_D2I_PRE:
- CRYPTO_BUFFER_free(ret->buf);
- ret->buf = NULL;
- break;
-
case ASN1_OP_D2I_POST: {
// The version must be one of v1(0), v2(1), or v3(2).
long version = X509_VERSION_1;
@@ -150,7 +144,6 @@
CRL_DIST_POINTS_free(ret->crldp);
GENERAL_NAMES_free(ret->altname);
NAME_CONSTRAINTS_free(ret->nc);
- CRYPTO_BUFFER_free(ret->buf);
break;
}
@@ -170,29 +163,20 @@
X509 *X509_parse_from_buffer(CRYPTO_BUFFER *buf) {
if (CRYPTO_BUFFER_len(buf) > LONG_MAX) {
OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
- return 0;
- }
-
- X509 *x509 = X509_new();
- if (x509 == NULL) {
return NULL;
}
- x509->cert_info->enc.alias_only_on_next_parse = 1;
-
+ // Pass |buf| into the parser so that the cached encoding in |X509_CINF| can
+ // alias into the original buffer and save some memory.
const uint8_t *inp = CRYPTO_BUFFER_data(buf);
- X509 *x509p = x509;
- X509 *ret = d2i_X509(&x509p, &inp, CRYPTO_BUFFER_len(buf));
- if (ret == NULL ||
- inp - CRYPTO_BUFFER_data(buf) != (ptrdiff_t)CRYPTO_BUFFER_len(buf)) {
- X509_free(x509p);
+ X509 *ret = NULL;
+ if (ASN1_item_ex_d2i((ASN1_VALUE **)&ret, &inp, CRYPTO_BUFFER_len(buf),
+ ASN1_ITEM_rptr(X509), /*tag=*/-1,
+ /*aclass=*/0, /*opt=*/0, buf) <= 0 ||
+ inp != CRYPTO_BUFFER_data(buf) + CRYPTO_BUFFER_len(buf)) {
+ X509_free(ret);
return NULL;
}
- assert(x509p == x509);
- assert(ret == x509);
-
- CRYPTO_BUFFER_up_ref(buf);
- ret->buf = buf;
return ret;
}