← Back to Feed CACHED · 2026-05-31 09:02:04 · CACHE_KEY CVE-2026-34875
CVE-2026-34875 · CWE-120 · Disclosed 2026-04-01

Mbed TLS / TF-PSA-Crypto FFDH public key export buffer overflow

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

This is less like a front-door break-in and more like a sharp edge hidden inside a seldom-used maintenance hatch

CVE-2026-34875 is a buffer overflow in psa_export_public_key() when handling FFDH public keys in Mbed TLS 3.5.0 through 3.6.5 and TF-PSA-Crypto 1.0.0. If an application exports an FFDH public key into a caller-provided buffer that is too small, the library can overwrite memory instead of returning the documented buffer-too-small error. Fixed upstream versions are Mbed TLS 3.6.6+ and TF-PSA-Crypto 1.1.0+.

The vendor-style 9.8/CRITICAL network-RCE framing overshoots reality for most enterprises. The bug is real and memory corruption always deserves respect, but this is not a generic TLS handshake parser bug and not a broadly reachable daemon flaw by default; an attacker needs a deployment that actually exposes a code path exporting an attacker-influenced FFDH key, plus an application integration that passes an undersized buffer. That stack of prerequisites is why this lands as MEDIUM, not CRITICAL.

"Serious memory corruption bug, but the real-world blast radius is cut down by a rare, non-default export path."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Find a reachable FFDH export path

The attacker first needs an application that uses Mbed TLS or TF-PSA-Crypto and exposes a path that eventually calls psa_export_public_key() for FFDH keys. This is typically a bespoke API, device-management workflow, crypto service, or firmware feature rather than a normal TLS listener. A practical weapon would be a bespoke client or API harness, not a stock internet exploit kit.
Conditions required:
  • Target runs Mbed TLS 3.5.0-3.6.5 or TF-PSA-Crypto 1.0.0
  • Application actually uses FFDH key material
  • An attacker can trigger public-key export through a reachable interface
Where this breaks in practice:
  • FFDH export is a niche path compared with normal TLS handshakes
  • Many deployments use ECDHE/X25519 and never touch this code path
  • Library presence alone does not imply exploitability
Detection/coverage: SCA/SBOM tools should flag the vulnerable library version, but network scanners will miss most exploitable instances because reachability depends on application logic, not a banner.
STEP 02

Control an export of an attacker-influenced key

The advisory says code execution may be possible if an attacker can cause the application to export an arbitrary FFDH key. In practice, that means the attacker must influence which key is exported or supply/import key material that later gets exported. A likely weapon is a custom protocol exerciser against import/generate/export flows, not a single-packet trigger.
Conditions required:
  • Application allows attacker-controlled or attacker-selectable FFDH key operations
  • Export policy or workflow permits the key to be exported
Where this breaks in practice:
  • Many key stores and secure-element designs do not expose export at all
  • Key usage policy may block export or copy operations
  • This often implies authenticated or semi-trusted workflow access, even if the CVSS vector says PR:N
Detection/coverage: Look for unusual sequences of key import/generation followed by export requests in application logs, HSM/SE logs, or API telemetry. Generic IDS signatures are unlikely to be useful.
STEP 03

Hit the undersized output buffer

The actual flaw occurs because the library writes the full FFDH public key even when the caller-provided buffer is too small, instead of failing with PSA_ERROR_BUFFER_TOO_SMALL as the PSA API documentation describes. Exploitation therefore depends on the surrounding application allocating the wrong size and then exposing that faulty path to attacker influence. A likely exploit primitive is a memory-corruption harness tailored to the target build, allocator, and architecture.
Conditions required:
  • Caller allocates too-small output buffer for the exported key
  • Target build and runtime memory layout make the overwrite useful
Where this breaks in practice:
  • Well-written callers use PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE() or PSA_EXPORT_PUBLIC_KEY_MAX_SIZE and avoid the bug
  • Modern hardening, watchdogs, and memory protections may turn this into a crash instead of code execution
  • Embedded targets vary heavily by compiler, ABI, and allocator, complicating reliable exploitation
Detection/coverage: Crash telemetry, watchdog resets, sanitizer builds, and fuzzing are more useful than perimeter monitoring here. EDR coverage is often absent or thin on the embedded/IoT platforms where Mbed TLS is common.
STEP 04

Convert corruption into impact

If the prior steps line up, the result could be denial of service, unstable crypto operations, or in the best case for the attacker, code execution inside the calling process. But the exploit has to be shaped for a very specific app-level integration, which sharply limits mass exploitation. This is a targeted exploit-development problem, not an opportunistic internet spray issue.
Conditions required:
  • Corrupted memory lands on a controllable object or return path
  • The target process has meaningful privileges or operational importance
Where this breaks in practice:
  • Reliability is build-specific
  • Blast radius is usually the single process or device using the vulnerable export path
  • No current evidence of broad in-the-wild weaponization
Detection/coverage: Post-crash forensic evidence may be sparse on appliances and microcontrollers. Runtime anomaly monitoring on the calling application is your best bet where available.
03 · Compensating Control

1
Inventory every Mbed TLS and TF-PSA-Crypto consumer — Use SBOM, package inventory, firmware BOMs, and source-code search to find where psa_export_public_key() and FFDH are actually used. For a MEDIUM finding there is no mitigation SLA — go straight to the 365-day remediation window, but do the inventory early because the hard part here is exposure mapping, not patch mechanics.
2
Disable or avoid FFDH export paths — Where you own the application, remove or gate FFDH public-key export features, and prefer modern curves that do not touch this code path. There is no mitigation SLA for MEDIUM, so use this as a targeted hardening step where patching is delayed or the software is embedded in longer-lived devices.
3
Enforce safe PSA buffer sizing — Review callers to ensure they allocate export buffers with PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE() or PSA_EXPORT_PUBLIC_KEY_MAX_SIZE, exactly as the PSA API documents. This is the best temporary engineering control for custom apps while you work toward the 365-day remediation deadline.
4
Restrict key export operations — Tighten authorization around any management/API workflow that can generate, import, or export asymmetric keys, especially on appliances and device-control planes. Even without a mitigation SLA, shrinking who can reach export operations materially reduces exploitability in the interim.
5
Fuzz and crash-test custom integrations — If you build products on top of Mbed TLS, run targeted fuzzing and negative tests around FFDH key export and key-management APIs. This does not replace patching, but it helps you separate mere library presence from actually exploitable integrations before the remediation window closes.
What doesn't work
  • A WAF will not save you unless the vulnerable path happens to sit behind an HTTP API you fully understand; the bug is in application-side memory handling, not a recognizable web payload pattern.
  • Perimeter network scanning does not tell you whether the export path is reachable. Seeing Mbed TLS in a binary or on a banner is not the same as proving exploitable exposure for this CVE.
  • MFA is irrelevant when the vulnerable workflow is unauthenticated, and only partially helpful when the real issue is a flawed internal export path after auth.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo confirmed exploitation found in the sources reviewed; the CISA KEV catalog does not list CVE-2026-34875.
Proof-of-concept availabilityNo public PoC located in vendor, NVD, Debian, Ubuntu, or GitHub release/advisory sources reviewed. That does not prove none exists, but there is no obvious commodity exploit trail.
EPSS0.00057 from your intel block — very low predicted near-term exploitation probability.
KEV statusNot KEV-listed as of the CISA KEV catalog page reviewed; no date added applies.
CVSS vector reality checkCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H assumes a directly reachable network trigger. The vendor advisory instead describes an FFDH public-key export API bug, which is materially narrower than a generic exposed service flaw.
Affected versionsMbed TLS 3.5.0–3.6.5 and TF-PSA-Crypto 1.0.0 per the upstream advisory.
Fixed versionsMbed TLS 3.6.6+ and TF-PSA-Crypto 1.1.0+ upstream. Debian tracker shows sid fixed at 3.6.6-0.1; Ubuntu currently marks affected packages as Needs evaluation and its OSV page shows Ubuntu priority medium.
Scanner/exposure dataInternet-wide scan data is low-value for this CVE. This is a library-internal export path, not a distinct bannerable service; finding Mbed TLS on the wire does not prove the vulnerable function is reachable. That is an inference from the advisory plus the PSA API call semantics.
Disclosure date2026-04-01 in NVD / user intel; the upstream advisory is dated 2026-03-31.
Reporter / creditsCredited upstream to Haruto Kimura (Stella).

Sources.

  1. Mbed TLS advisory: CVE-2026-34875
  2. NVD entry
  3. PSA Crypto API key export documentation
  4. Mbed TLS 3.6.6 release
  5. TF-PSA-Crypto changelog showing 1.1.0 branch release
  6. Debian security tracker
  7. Ubuntu CVE page
  8. CISA Known Exploited Vulnerabilities catalog
05 · The Call

Final Verdict
↓ DOWNGRADED to MEDIUM (5.8/10)

Why this verdict

  • Vendor 9.8 assumes a socket to the bug. The actual flaw is in psa_export_public_key() for FFDH keys, so I am cutting severity because this is not equivalent to unauthenticated packet-level reachability on every service linked against Mbed TLS.
  • Reachability penalty: requires FFDH export, not just library presence. The target must use FFDH and expose a workflow that exports the public key; that is a strong narrowing factor in real deployments.
  • Caller-integration penalty: exploitation needs an undersized application buffer. Well-implemented callers using PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE() or PSA_EXPORT_PUBLIC_KEY_MAX_SIZE should avoid the dangerous state entirely, which pushes this away from mass-exploitable territory.
  • Telemetry is cold. Your EPSS is 0.00057, there is no KEV listing, and I found no obvious public PoC in primary-source review, so there is no evidence-based reason to keep this in the CRITICAL bucket.

Why not higher?

Because the exploit chain is application-specific and non-default. This is not a wormable listener bug, not a routine TLS handshake parsing issue, and not something a scanner can reliably prove reachable from the network just because the library exists. The vendor's network/PR:N framing describes theoretical impact, not typical enterprise exposure.

Why not lower?

Because the underlying bug is still memory corruption in a cryptographic library, and the vendor explicitly states arbitrary code execution may be possible if an attacker can drive export of an arbitrary FFDH key. If you run custom crypto services, embedded management planes, or device firmware that exposes PSA key workflows, the impact can still be ugly on the subset that is actually reachable.

06 · Verification

Crowdsourced verification payload.

Run this on the target host, firmware build root, or extracted container/image filesystem where the library is installed. Invoke it as python3 check_cve_2026_34875.py / or against an offline root like python3 check_cve_2026_34875.py /mnt/rootfs; no admin rights are required beyond read access to package metadata and header files.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# CVE-2026-34875 checker for Mbed TLS / TF-PSA-Crypto
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

import os
import re
import sys
import subprocess
from pathlib import Path

ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else "/")

MBED_VULN_MIN = (3, 5, 0)
MBED_PATCHED = (3, 6, 6)
TF_VULN_EXACT = (1, 0, 0)
TF_PATCHED = (1, 1, 0)

results = []
notes = []


def norm_root(p):
    p = str(p)
    if ROOT == Path("/"):
        return p
    return str(ROOT / p.lstrip("/"))


def parse_semver(text):
    m = re.search(r'(\d+)\.(\d+)\.(\d+)', text or '')
    if not m:
        return None
    return tuple(int(x) for x in m.groups())


def cmp_ver(a, b):
    return (a > b) - (a < b)


def assess_mbed(version_text, source):
    ver = parse_semver(version_text)
    if not ver:
        results.append(("UNKNOWN", f"Mbed TLS version unreadable from {source}: {version_text}"))
        return

    # Distro/backport ambiguity: if package version has a distro suffix and upstream part is old,
    # the package may contain a backport not visible from the plain semver.
    if re.search(r'(ubuntu|deb|el\d|amzn|alpine|sles|fc\d)', version_text, re.I):
        results.append(("UNKNOWN", f"Mbed TLS distro package version needs vendor advisory validation: {source}={version_text}"))
        return

    if cmp_ver(ver, MBED_VULN_MIN) >= 0 and cmp_ver(ver, MBED_PATCHED) < 0:
        results.append(("VULNERABLE", f"Mbed TLS vulnerable version from {source}: {version_text}"))
    elif cmp_ver(ver, MBED_PATCHED) >= 0:
        results.append(("PATCHED", f"Mbed TLS patched version from {source}: {version_text}"))
    else:
        results.append(("PATCHED", f"Mbed TLS version older than affected range from {source}: {version_text}"))


def assess_tf(version_text, source):
    ver = parse_semver(version_text)
    if not ver:
        results.append(("UNKNOWN", f"TF-PSA-Crypto version unreadable from {source}: {version_text}"))
        return

    if re.search(r'(ubuntu|deb|el\d|amzn|alpine|sles|fc\d)', version_text, re.I):
        results.append(("UNKNOWN", f"TF-PSA-Crypto distro package version needs vendor advisory validation: {source}={version_text}"))
        return

    if ver == TF_VULN_EXACT:
        results.append(("VULNERABLE", f"TF-PSA-Crypto vulnerable version from {source}: {version_text}"))
    elif cmp_ver(ver, TF_PATCHED) >= 0:
        results.append(("PATCHED", f"TF-PSA-Crypto patched version from {source}: {version_text}"))
    else:
        results.append(("UNKNOWN", f"TF-PSA-Crypto unusual version from {source}: {version_text}"))


def read_file(path):
    try:
        with open(path, 'r', encoding='utf-8', errors='ignore') as f:
            return f.read()
    except Exception:
        return None


def check_headers():
    header_candidates = [
        norm_root('/usr/include/mbedtls/version.h'),
        norm_root('/usr/local/include/mbedtls/version.h'),
        norm_root('/opt/include/mbedtls/version.h'),
        norm_root('/include/mbedtls/version.h'),
    ]
    tf_candidates = [
        norm_root('/usr/include/tf-psa-crypto/version.h'),
        norm_root('/usr/local/include/tf-psa-crypto/version.h'),
        norm_root('/opt/include/tf-psa-crypto/version.h'),
        norm_root('/include/tf-psa-crypto/version.h'),
    ]

    for p in header_candidates:
        if os.path.isfile(p):
            data = read_file(p)
            if data:
                m = re.search(r'MBEDTLS_VERSION_STRING\s+"([^"]+)"', data)
                if m:
                    assess_mbed(m.group(1), p)
                    return True
    for p in tf_candidates:
        if os.path.isfile(p):
            data = read_file(p)
            if data:
                m = re.search(r'TF_PSA_CRYPTO_VERSION_STRING\s+"([^"]+)"', data)
                if m:
                    assess_tf(m.group(1), p)
                    return True
    return False


def run_cmd(cmd):
    try:
        out = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True)
        return out.strip()
    except Exception:
        return None


def check_pkg_config():
    if ROOT != Path('/'):
        return False
    hit = False
    for name in ['mbedtls', 'tf-psa-crypto']:
        out = run_cmd(['pkg-config', '--modversion', name])
        if out:
            hit = True
            if name == 'mbedtls':
                assess_mbed(out, f'pkg-config:{name}')
            else:
                assess_tf(out, f'pkg-config:{name}')
    return hit


def check_dpkg_status():
    status_path = norm_root('/var/lib/dpkg/status')
    if not os.path.isfile(status_path):
        return False
    data = read_file(status_path)
    if not data:
        return False
    hit = False
    for block in data.split('\n\n'):
        pkg = re.search(r'^Package:\s*(.+)$', block, re.M)
        ver = re.search(r'^Version:\s*(.+)$', block, re.M)
        if not pkg or not ver:
            continue
        name = pkg.group(1).strip()
        version = ver.group(1).strip()
        if name in ('libmbedtls-dev', 'libmbedtls21', 'libmbedtls14', 'libmbedtls12', 'libmbedcrypto16', 'libmbedcrypto7', 'libmbedcrypto3', 'mbedtls'):
            hit = True
            assess_mbed(version, f'dpkg:{name}')
        elif name in ('tf-psa-crypto', 'libtf-psa-crypto-dev'):
            hit = True
            assess_tf(version, f'dpkg:{name}')
    return hit


def check_apk_installed():
    path = norm_root('/lib/apk/db/installed')
    if not os.path.isfile(path):
        return False
    data = read_file(path)
    if not data:
        return False
    hit = False
    name = None
    ver = None
    for line in data.splitlines() + ['']:
        if line.startswith('P:'):
            name = line[2:].strip()
        elif line.startswith('V:'):
            ver = line[2:].strip()
        elif line == '':
            if name and ver:
                if 'mbedtls' in name:
                    hit = True
                    assess_mbed(ver, f'apk:{name}')
                elif 'tf-psa-crypto' in name:
                    hit = True
                    assess_tf(ver, f'apk:{name}')
            name = None
            ver = None
    return hit


def check_rpm_db():
    if ROOT != Path('/'):
        return False
    out = run_cmd(['rpm', '-qa', '--qf', '%{NAME} %{VERSION}-%{RELEASE}\n'])
    if not out:
        return False
    hit = False
    for line in out.splitlines():
        parts = line.split(None, 1)
        if len(parts) != 2:
            continue
        name, version = parts
        if 'mbedtls' in name:
            hit = True
            assess_mbed(version, f'rpm:{name}')
        elif 'tf-psa-crypto' in name:
            hit = True
            assess_tf(version, f'rpm:{name}')
    return hit


def summarize():
    if not results:
        print('UNKNOWN')
        print('No Mbed TLS or TF-PSA-Crypto evidence found in common package DBs or headers.')
        sys.exit(2)

    for status, msg in results:
        notes.append(f'[{status}] {msg}')

    has_vuln = any(status == 'VULNERABLE' for status, _ in results)
    has_patched = any(status == 'PATCHED' for status, _ in results)
    has_unknown = any(status == 'UNKNOWN' for status, _ in results)

    if has_vuln:
        print('VULNERABLE')
        for n in notes:
            print(n)
        sys.exit(1)
    if has_patched and not has_unknown:
        print('PATCHED')
        for n in notes:
            print(n)
        sys.exit(0)
    print('UNKNOWN')
    for n in notes:
        print(n)
    sys.exit(2)


if __name__ == '__main__':
    found = False
    found |= check_pkg_config()
    found |= check_dpkg_status()
    found |= check_apk_installed()
    found |= check_rpm_db()
    found |= check_headers()
    summarize()