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.
4 steps from start to impact.
Find a reachable FFDH export path
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.- 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
- 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
Control an export of an attacker-influenced key
- Application allows attacker-controlled or attacker-selectable FFDH key operations
- Export policy or workflow permits the key to be exported
- 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
Hit the undersized output buffer
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.- Caller allocates too-small output buffer for the exported key
- Target build and runtime memory layout make the overwrite useful
- Well-written callers use
PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE()orPSA_EXPORT_PUBLIC_KEY_MAX_SIZEand 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
Convert corruption into impact
- Corrupted memory lands on a controllable object or return path
- The target process has meaningful privileges or operational importance
- 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
The supporting signals.
| In-the-wild status | No confirmed exploitation found in the sources reviewed; the CISA KEV catalog does not list CVE-2026-34875. |
|---|---|
| Proof-of-concept availability | No 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. |
| EPSS | 0.00057 from your intel block — very low predicted near-term exploitation probability. |
| KEV status | Not KEV-listed as of the CISA KEV catalog page reviewed; no date added applies. |
| CVSS vector reality check | CVSS: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 versions | Mbed TLS 3.5.0–3.6.5 and TF-PSA-Crypto 1.0.0 per the upstream advisory. |
| Fixed versions | Mbed 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 data | Internet-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 date | 2026-04-01 in NVD / user intel; the upstream advisory is dated 2026-03-31. |
| Reporter / credits | Credited upstream to Haruto Kimura (Stella). |
noisgate verdict.
The decisive downgrade factor is reachability: this bug lives in an FFDH public-key export path, not in a broadly exposed handshake parser or default listener. Most enterprises with Mbed TLS deployed will have the vulnerable code present on disk, but only a much smaller subset will expose an attacker-reachable workflow that both uses FFDH and mishandles export buffer sizing.
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()orPSA_EXPORT_PUBLIC_KEY_MAX_SIZEshould 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.
What to do — in priority order.
- 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. - 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.
- Enforce safe PSA buffer sizing — Review callers to ensure they allocate export buffers with
PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE()orPSA_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. - 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.
- 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.
- 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.
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.
#!/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()
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.