Skip to content

Commit edbdc24

Browse files
davidbenBoringssl LUCI CQ
authored and
Boringssl LUCI CQ
committed
Reject [UNIVERSAL 0] in DER/BER element parsers.
[UNIVERSAL 0] is reserved by X.680 for the encoding to use. BER uses this to encode indefinite-length EOCs, but it is possible to encode it in a definite-length element or in a non-EOC form (non-zero length, or constructed). Whether we accept such encodings is normally moot: parsers will reject the tag as unsuitable for the type. However, the ANY type matches all tags. Previously, we would allow this, but crypto/asn1 has some ad-hoc checks for unexpected EOCs, in some contexts, but not others. Generalize this check to simply rejecting [UNIVERSAL 0] in all forms. This avoids a weird hole in the abstraction where tags are sometimes representable in BER and sometimes not. It also means we'll preserve this check when migrating parsers from crypto/asn1. Update-Note: There are two kinds of impacts I might expect from this change. The first is BER parsers might be relying on the CBS DER/BER element parser to pick up EOCs, as our ber.c does. This should be caught by the most basic unit test and can be fixed by detecting EOCs externally. The second is code might be trying to parse "actual" elements with tag [UNIVERSAL 0]. No actual types use this tag, so any non-ANY field is already rejecting such inputs. However, it is possible some input has this tag in a field with type ANY. This CL will cause us to reject that input. Note, however, that crypto/asn1 already rejects unexpected EOCs inside sequences, so many cases were already rejected anyway. Such inputs are also invalid as the ANY should match some actual, unknown ASN.1 type, and that type cannot use the reserved tag. Fixed: 455 Change-Id: If42cacc01840439059baa0e67179d0f198234fc4 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/52245 Reviewed-by: Adam Langley <[email protected]> Commit-Queue: David Benjamin <[email protected]>
1 parent 2fc6d38 commit edbdc24

File tree

6 files changed

+70
-42
lines changed

6 files changed

+70
-42
lines changed

crypto/asn1/asn1_test.cc

+41
Original file line numberDiff line numberDiff line change
@@ -2075,6 +2075,47 @@ TEST(ASN1Test, GetObject) {
20752075
sizeof(kIndefinite)));
20762076
}
20772077

2078+
template <typename T>
2079+
void ExpectNoParse(T *(*d2i)(T **, const uint8_t **, long),
2080+
const std::vector<uint8_t> &in) {
2081+
SCOPED_TRACE(Bytes(in));
2082+
const uint8_t *ptr = in.data();
2083+
bssl::UniquePtr<T> obj(d2i(nullptr, &ptr, in.size()));
2084+
EXPECT_FALSE(obj);
2085+
}
2086+
2087+
// The zero tag, constructed or primitive, is reserved and should rejected by
2088+
// the parser.
2089+
TEST(ASN1Test, ZeroTag) {
2090+
ExpectNoParse(d2i_ASN1_TYPE, {0x00, 0x00});
2091+
ExpectNoParse(d2i_ASN1_TYPE, {0x00, 0x10, 0x00});
2092+
ExpectNoParse(d2i_ASN1_TYPE, {0x20, 0x00});
2093+
ExpectNoParse(d2i_ASN1_TYPE, {0x20, 0x00});
2094+
ExpectNoParse(d2i_ASN1_SEQUENCE_ANY, {0x30, 0x02, 0x00, 0x00});
2095+
ExpectNoParse(d2i_ASN1_SET_ANY, {0x31, 0x02, 0x00, 0x00});
2096+
// SEQUENCE {
2097+
// OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.1 }
2098+
// [UNIVERSAL 0 PRIMITIVE] {}
2099+
// }
2100+
ExpectNoParse(d2i_X509_ALGOR,
2101+
{0x30, 0x10, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
2102+
0x04, 0x01, 0x84, 0xb7, 0x09, 0x01, 0x00, 0x00});
2103+
// SEQUENCE {
2104+
// OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.1 }
2105+
// [UNIVERSAL 0 CONSTRUCTED] {}
2106+
// }
2107+
ExpectNoParse(d2i_X509_ALGOR,
2108+
{0x30, 0x10, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
2109+
0x04, 0x01, 0x84, 0xb7, 0x09, 0x01, 0x20, 0x00});
2110+
// SEQUENCE {
2111+
// OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.1 }
2112+
// [UNIVERSAL 0 PRIMITIVE] { "a" }
2113+
// }
2114+
ExpectNoParse(d2i_X509_ALGOR,
2115+
{0x30, 0x11, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
2116+
0x04, 0x01, 0x84, 0xb7, 0x09, 0x01, 0x00, 0x01, 0x61});
2117+
}
2118+
20782119
// The ASN.1 macros do not work on Windows shared library builds, where usage of
20792120
// |OPENSSL_EXPORT| is a bit stricter.
20802121
#if !defined(OPENSSL_WINDOWS) || !defined(BORINGSSL_SHARED_LIBRARY)

crypto/asn1/tasn_dec.c

-31
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@
7474
*/
7575
#define ASN1_MAX_CONSTRUCTED_NEST 30
7676

77-
static int asn1_check_eoc(const unsigned char **in, long len);
78-
7977
static int asn1_check_tlen(long *olen, int *otag, unsigned char *oclass,
8078
char *cst, const unsigned char **in, long len,
8179
int exptag, int expclass, char opt, ASN1_TLC *ctx);
@@ -373,13 +371,6 @@ static int asn1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in,
373371
if (!len)
374372
break;
375373
q = p;
376-
/* TODO(https://crbug.com/boringssl/455): Although we've removed
377-
* indefinite-length support, this check is not quite a no-op.
378-
* Reject [UNIVERSAL 0] in the tag parsers themselves. */
379-
if (asn1_check_eoc(&p, len)) {
380-
OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNEXPECTED_EOC);
381-
goto err;
382-
}
383374
/*
384375
* This determines the OPTIONAL flag value. The field cannot be
385376
* omitted if it is the last of a SEQUENCE and there is still
@@ -592,13 +583,6 @@ static int asn1_template_noexp_d2i(ASN1_VALUE **val,
592583
while (len > 0) {
593584
ASN1_VALUE *skfield;
594585
const unsigned char *q = p;
595-
/* TODO(https://crbug.com/boringssl/455): Although we've removed
596-
* indefinite-length support, this check is not quite a no-op.
597-
* Reject [UNIVERSAL 0] in the tag parsers themselves. */
598-
if (asn1_check_eoc(&p, len)) {
599-
OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNEXPECTED_EOC);
600-
goto err;
601-
}
602586
skfield = NULL;
603587
if (!asn1_item_ex_d2i(&skfield, &p, len, ASN1_ITEM_ptr(tt->item),
604588
-1, 0, 0, ctx, depth)) {
@@ -868,21 +852,6 @@ static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len,
868852
return ret;
869853
}
870854

871-
/* Check for ASN1 EOC and swallow it if found */
872-
873-
static int asn1_check_eoc(const unsigned char **in, long len)
874-
{
875-
const unsigned char *p;
876-
if (len < 2)
877-
return 0;
878-
p = *in;
879-
if (!p[0] && !p[1]) {
880-
*in += 2;
881-
return 1;
882-
}
883-
return 0;
884-
}
885-
886855
/*
887856
* Check an ASN1 tag and length: a bit like ASN1_get_object but it handles
888857
* the ASN1_TLC cache and checks the expected tag.

crypto/bytestring/ber.c

+12-10
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,14 @@ static int cbs_find_ber(const CBS *orig_in, int *ber_found, unsigned depth) {
9393
return 1;
9494
}
9595

96-
// is_eoc returns true if |header_len| and |contents|, as returned by
97-
// |CBS_get_any_ber_asn1_element|, indicate an "end of contents" (EOC) value.
98-
static char is_eoc(size_t header_len, CBS *contents) {
99-
return header_len == 2 && CBS_len(contents) == 2 &&
100-
OPENSSL_memcmp(CBS_data(contents), "\x00\x00", 2) == 0;
96+
// cbs_get_eoc returns one if |cbs| begins with an "end of contents" (EOC) value
97+
// and zero otherwise. If an EOC was found, it advances |cbs| past it.
98+
static int cbs_get_eoc(CBS *cbs) {
99+
if (CBS_len(cbs) >= 2 &&
100+
CBS_data(cbs)[0] == 0 && CBS_data(cbs)[1] == 0) {
101+
return CBS_skip(cbs, 2);
102+
}
103+
return 0;
101104
}
102105

103106
// cbs_convert_ber reads BER data from |in| and writes DER data to |out|. If
@@ -116,21 +119,20 @@ static int cbs_convert_ber(CBS *in, CBB *out, unsigned string_tag,
116119
}
117120

118121
while (CBS_len(in) > 0) {
122+
if (looking_for_eoc && cbs_get_eoc(in)) {
123+
return 1;
124+
}
125+
119126
CBS contents;
120127
unsigned tag, child_string_tag = string_tag;
121128
size_t header_len;
122129
int indefinite;
123130
CBB *out_contents, out_contents_storage;
124-
125131
if (!CBS_get_any_ber_asn1_element(in, &contents, &tag, &header_len,
126132
/*out_ber_found=*/NULL, &indefinite)) {
127133
return 0;
128134
}
129135

130-
if (is_eoc(header_len, &contents)) {
131-
return looking_for_eoc;
132-
}
133-
134136
if (string_tag != 0) {
135137
// This is part of a constructed string. All elements must match
136138
// |string_tag| up to the constructed bit and get appended to |out|

crypto/bytestring/bytestring_test.cc

+6-1
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,6 @@ struct BERTest {
699699

700700
static const BERTest kBERTests[] = {
701701
// Trivial cases, also valid DER.
702-
{"0000", true, false, false, 0},
703702
{"0100", true, false, false, 1},
704703
{"020101", true, false, false, 2},
705704

@@ -725,6 +724,12 @@ static const BERTest kBERTests[] = {
725724
{"1f4000", true, false, false, 0x40},
726725
// Non-minimal tags are invalid, even in BER.
727726
{"1f804000", false, false, false, 0},
727+
728+
// EOCs and other forms of tag [UNIVERSAL 0] are rejected as elements.
729+
{"0000", false, false, false, 0},
730+
{"000100", false, false, false, 0},
731+
{"00800000", false, false, false, 0},
732+
{"2000", false, false, false, 0},
728733
};
729734

730735
TEST(CBSTest, BERElementTest) {

crypto/bytestring/cbs.c

+7
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,13 @@ static int parse_asn1_tag(CBS *cbs, unsigned *out) {
279279

280280
tag |= tag_number;
281281

282+
// Tag [UNIVERSAL 0] is reserved for use by the encoding. Reject it here to
283+
// avoid some ambiguity around ANY values and BER indefinite-length EOCs. See
284+
// https://crbug.com/boringssl/455.
285+
if ((tag & ~CBS_ASN1_CONSTRUCTED) == 0) {
286+
return 0;
287+
}
288+
282289
*out = tag;
283290
return 1;
284291
}

include/openssl/bytestring.h

+4
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ OPENSSL_EXPORT int CBS_get_any_asn1_element(CBS *cbs, CBS *out,
265265
// also true for empty elements so |*out_indefinite| should be checked). If
266266
// |out_ber_found| is not NULL then it is set to one if any case of invalid DER
267267
// but valid BER is found, and to zero otherwise.
268+
//
269+
// This function will not successfully parse an end-of-contents (EOC) as an
270+
// element. Callers parsing indefinite-length encoding must check for EOC
271+
// separately.
268272
OPENSSL_EXPORT int CBS_get_any_ber_asn1_element(CBS *cbs, CBS *out,
269273
unsigned *out_tag,
270274
size_t *out_header_len,

0 commit comments

Comments
 (0)