Skip to content

Commit c3829dd

Browse files
mattcaswelllevitte
authored andcommitted
Fix a UAF resulting from a bug in BIO_new_NDEF
If the aux->asn1_cb() call fails in BIO_new_NDEF then the "out" BIO will be part of an invalid BIO chain. This causes a "use after free" when the BIO is eventually freed. Based on an original patch by Viktor Dukhovni and an idea from Theo Buehler. Thanks to Octavio Galland for reporting this issue. Reviewed-by: Paul Dale <pauli@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org>
1 parent 2bd6112 commit c3829dd

File tree

1 file changed

+32
-7
lines changed

1 file changed

+32
-7
lines changed

crypto/asn1/bio_ndef.c

+32-7
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,19 @@ static int ndef_suffix(BIO *b, unsigned char **pbuf, int *plen, void *parg);
4949
static int ndef_suffix_free(BIO *b, unsigned char **pbuf, int *plen,
5050
void *parg);
5151

52+
/*
53+
* On success, the returned BIO owns the input BIO as part of its BIO chain.
54+
* On failure, NULL is returned and the input BIO is owned by the caller.
55+
*
56+
* Unfortunately cannot constify this due to CMS_stream() and PKCS7_stream()
57+
*/
5258
BIO *BIO_new_NDEF(BIO *out, ASN1_VALUE *val, const ASN1_ITEM *it)
5359
{
5460
NDEF_SUPPORT *ndef_aux = NULL;
5561
BIO *asn_bio = NULL;
5662
const ASN1_AUX *aux = it->funcs;
5763
ASN1_STREAM_ARG sarg;
64+
BIO *pop_bio = NULL;
5865

5966
if (!aux || !aux->asn1_cb) {
6067
ASN1err(ASN1_F_BIO_NEW_NDEF, ASN1_R_STREAMING_NOT_SUPPORTED);
@@ -69,33 +76,51 @@ BIO *BIO_new_NDEF(BIO *out, ASN1_VALUE *val, const ASN1_ITEM *it)
6976
out = BIO_push(asn_bio, out);
7077
if (out == NULL)
7178
goto err;
79+
pop_bio = asn_bio;
7280

73-
BIO_asn1_set_prefix(asn_bio, ndef_prefix, ndef_prefix_free);
74-
BIO_asn1_set_suffix(asn_bio, ndef_suffix, ndef_suffix_free);
81+
if (BIO_asn1_set_prefix(asn_bio, ndef_prefix, ndef_prefix_free) <= 0
82+
|| BIO_asn1_set_suffix(asn_bio, ndef_suffix, ndef_suffix_free) <= 0
83+
|| BIO_ctrl(asn_bio, BIO_C_SET_EX_ARG, 0, ndef_aux) <= 0)
84+
goto err;
7585

7686
/*
77-
* Now let callback prepends any digest, cipher etc BIOs ASN1 structure
78-
* needs.
87+
* Now let the callback prepend any digest, cipher, etc., that the BIO's
88+
* ASN1 structure needs.
7989
*/
8090

8191
sarg.out = out;
8292
sarg.ndef_bio = NULL;
8393
sarg.boundary = NULL;
8494

85-
if (aux->asn1_cb(ASN1_OP_STREAM_PRE, &val, it, &sarg) <= 0)
95+
/*
96+
* The asn1_cb(), must not have mutated asn_bio on error, leaving it in the
97+
* middle of some partially built, but not returned BIO chain.
98+
*/
99+
if (aux->asn1_cb(ASN1_OP_STREAM_PRE, &val, it, &sarg) <= 0) {
100+
/*
101+
* ndef_aux is now owned by asn_bio so we must not free it in the err
102+
* clean up block
103+
*/
104+
ndef_aux = NULL;
86105
goto err;
106+
}
107+
108+
/*
109+
* We must not fail now because the callback has prepended additional
110+
* BIOs to the chain
111+
*/
87112

88113
ndef_aux->val = val;
89114
ndef_aux->it = it;
90115
ndef_aux->ndef_bio = sarg.ndef_bio;
91116
ndef_aux->boundary = sarg.boundary;
92117
ndef_aux->out = out;
93118

94-
BIO_ctrl(asn_bio, BIO_C_SET_EX_ARG, 0, ndef_aux);
95-
96119
return sarg.ndef_bio;
97120

98121
err:
122+
/* BIO_pop() is NULL safe */
123+
(void)BIO_pop(pop_bio);
99124
BIO_free(asn_bio);
100125
OPENSSL_free(ndef_aux);
101126
return NULL;

0 commit comments

Comments
 (0)