# CVE-2026-63269 — Root Cause Analysis

## Summary

LibreOffice documents can contain *linked* audio/video media objects (ODF
`draw:plugin` inside a `draw:frame`, e.g. an Impress `MediaShape`). On Linux,
LibreOffice plays such media through its `avmedia` GStreamer backend. In
vulnerable versions, merely **opening** a document causes LibreOffice to hand
the linked media URL to GStreamer's `playbin`, which resolves and prerolls the
stream without any user interaction or link-update consent. If the linked
media is an HLS playlist (`.m3u8`), GStreamer's `hlsdemux` follows every URI
listed in the playlist — including `file://` URIs naming local files and
`http(s)://` URLs naming arbitrary remote hosts — so a crafted document makes
the victim's machine read local files and issue attacker-directed GET requests
simply by being opened.

## Impact

- **Package/component affected:** LibreOffice (avmedia GStreamer backend,
  `libavmediagst.so`; document media shapes in Impress/Draw).
- **Affected versions:** LibreOffice < 26.2.5 / < 26.8.0 (reproduced on the
  TDF 26.2.4.2 Linux x86-64 deb build).
- **Risk level and consequences:** Medium (CVSS 6.7 per advisory). Remote,
  unauthenticated attacker delivers a document; on open, the victim's
  LibreOffice performs:
  - **LFI:** local files named in the playlist are opened and read by
    GStreamer (`filesrc`), and their contents can end up in the document
    (per the advisory), enabling file disclosure.
  - **GET SSRF:** arbitrary remote URLs listed in the playlist are fetched
    from the victim host (here: `GStreamer souphttpsrc` HTTP requests),
    reaching internal/loopback services.

## Impact Parity

- **Disclosed/claimed maximum impact:** LFI + GET SSRF via GStreamer following
  a document-linked HLS playlist (`expected_impact=ssrf`, surface
  `viewer_document`, entrypoint `open_document`).
- **Reproduced impact from this run:** Full LFI + GET SSRF on document open
  through the real product (LibreOffice Impress 26.2.4.2 under Xvfb):
  the attacker HTTP server received the playlist GET and the SSRF segment GET
  from `GStreamer souphttpsrc`, and `strace` captured the soffice/GStreamer
  process tree opening the local secret file referenced by a `file://`
  playlist entry. Two independent vulnerable attempts reproduced both effects;
  two fixed-version (26.2.5.2) attempts fetched nothing at all.
- **Parity:** `full`
- **Not demonstrated:** exfiltration of the local file's *contents* back to
  the attacker (the advisory's "contents could end up in the document" leg);
  the local read itself and the remote fetch are both proven.

## Root Cause

LibreOffice's media shape (`SdrMediaObj` / `avmedia::MediaWindow`) creates a
GStreamer `playbin` for the persisted linked-media URL as soon as the slide
containing the media object is displayed — which happens during document load
for the first page — and sets it to the paused/preroll state to render a
preview frame. Prerolling an HLS playlist makes `hlsdemux` download the
manifest and then fetch every fragment URI it lists. Neither LibreOffice nor
GStreamer restricted:

1. the scheme of fragment URIs inside a playlist served over HTTP
   (`file://` entries are honored by `filesrc`), or
2. the remote URLs a playlist can direct the player to (any `http(s)://`
   target is fetched by `souphttpsrc`).

Additionally, linked media was loaded automatically on document open without
being subject to LibreOffice's link-update consent.

**Fix (LibreOffice 26.2.5 / 26.8.0, by Caolán McNamara):** per the advisory,
"LibreOffice does not follow playlists that name further resources, and
linked media is under link update control." In this run's negative control,
the fixed 26.2.5.2 build did not even fetch the playlist when opening the same
crafted document (linked media now requires link-update consent, suppressed in
an unattended open), and a fortiori never followed playlist-listed resources.

## Reproduction Steps

1. `bundle/repro/reproduction_steps.sh` (self-contained; safe to re-run).
2. What it does:
   - Installs runtime deps (`xvfb`, `strace`, GStreamer good/bad/libav
     plugins) and downloads/extracts the official TDF deb builds
     LibreOffice 26.2.4.2 (vulnerable) and 26.2.5.2 (fixed) into the prepared
     project cache.
   - Generates a valid MPEG-TS fragment (so playback genuinely proceeds
     through the playlist instead of erroring on undecodable bytes).
   - For each attempt (2 vulnerable + 2 fixed), it:
     - starts a local "attacker" HTTP server (python3) that logs every
       request and serves `/stream.m3u8`;
     - writes an HLS playlist listing one remote URL
       (`http://127.0.0.1:PORT/ssrf-segment-<tag>-<n>.ts` — the SSRF probe)
       and one local file (`file://.../lfi-secret.txt` — the LFI probe);
     - generates a crafted ODP whose slide 1 contains a linked media object
       (`draw:plugin` with `draw:mime-type="application/vnd.sun.star.media"`)
       pointing at the playlist;
     - opens the document with the real `soffice` under Xvfb, tracing
       `openat()` with `strace -f`;
     - records playlist GETs, segment GETs, and local-file opens.
3. Expected evidence of reproduction:
   - Vulnerable attempts: `http.log` shows `GET /stream.m3u8` **and**
     `GET /ssrf-segment-...` with User-Agent `GStreamer souphttpsrc`;
     `lfi-evidence.txt` shows `openat(... "lfi-secret.txt" ...) = <fd>`.
   - Fixed attempts: neither the playlist nor the segment is fetched and the
     secret file is never opened.

## Evidence

- Full run log: `bundle/logs/reproduction_steps.log` (run 1) and
  `bundle/logs/reproduction_steps_run2.log` (run 2).
- Per-attempt proof under `bundle/repro/proof/<tag>-<n>/`:
  - `http.log` — attacker-server request log, e.g. vulnerable-2:
    `GET /stream.m3u8 ua=GStreamer souphttpsrc 1.28.2 libsoup/3.6.6` followed
    by `GET /ssrf-segment-vulnerable-2.ts` (same UA).
  - `lfi-evidence.txt` — strace excerpt, e.g. vulnerable-1:
    `openat(AT_FDCWD, ".../proof/vulnerable-1/lfi-secret.txt", O_RDONLY) = 40`.
  - `stream.m3u8` — the exact playlist served; `soffice.log` — product stderr.
- Machine-readable manifest with artifact hashes:
  `bundle/repro/runtime_manifest.json`.
- Verdict: `bundle/repro/validation_verdict.json`.
- Environment: Ubuntu 26.04 x86_64, LibreOffice 26.2.4.2 TDF deb (tarball
  sha256 `810ef197…4b9900`) vs LibreOffice 26.2.5.2 TDF deb (tarball sha256
  `2f03bfb2…1bed1e`), GStreamer 1.28.2, Xvfb display, no sanitizers —
  real product binaries exercised through the real document-open path.

## Recommendations / Next Steps

- Upgrade to LibreOffice ≥ 26.2.5 / ≥ 26.8.0 (fixed).
- The fix approach per the advisory: do not follow playlists that name further
  resources in the media backend, and put linked media behind link-update
  consent.
- Testing: open a document with a linked-media HLS playlist referencing
  `file://` and remote URLs; assert no outbound requests and no local file
  opens occur without explicit link-update consent.

## Additional Notes

- **Idempotency:** the script was run twice consecutively; both runs produced
  the confirmed verdict (2/2 vulnerable attempts positive, 2/2 fixed attempts
  negative each run). Downloads/extractions are cached in the prepared project
  cache and reused across runs; proof directories are regenerated per run.
- **Document format detail:** the ODF media link must use
  `draw:mime-type="application/vnd.sun.star.media"`; using an HLS-specific
  MIME type makes the ODF importer drop the URL (verified by round-tripping
  through the vulnerable build itself). The structure was derived from a
  reference document created programmatically via UNO
  (`com.sun.star.drawing.MediaShape.MediaURL`).
- **Limitations:** content exfiltration of the read file into the saved
  document was not needed to prove the claim ("local file reads and remote
  fetches") and was not attempted; the read and fetch primitives are
  demonstrated directly.
