[security] qcow2 backing_file reads arbitrary host files into a guest disk #109

Closed
opened 2026-09-02 04:37:55 +00:00 by a73x · 2 comments
a73x commented 2026-09-02 04:37:55 +00:00 (Migrated from git.a73x.sh)

[claude 2026-09-02] Found by whole-tree security audit.

internal/agent/imagecache/imagecache.go:345 hands go-qcow2reader an *os.File, which satisfies its Namer interface. The library honours the qcow2 backing_file header: absolute paths are opened verbatim, relative ones (incl. ../..) resolve against the download dir, via os.Open as root. convert.Convert (line 394) reads every unallocated cluster through the backing image and the result becomes the guest root disk.

Attack: any principal in the tenant (PAT, session, MCP client) crafts a qcow2 whose backing file is /var/lib/eitri-agent/identity.json (host credential), /etc/shadow, another VM's disk or seed under the state dir, hosts it anywhere, POSTs a VM with its sha, boots it, reads the host file out of its own disk. image_sha256 is tenant-supplied so it is no barrier. Nothing in the tree references backing files.

Scope: hosts are single-tenant, so this is API-principal → host-root read within one tenant. Matters because the design deliberately keeps host material (host credential, guest host keys) off the API, and a short-lived PAT converts into a permanent host credential. Severity Medium.

Fix: refuse images that declare a backing file — fail closed at openImage (check the parsed qcow2 header's BackingFileOffset / BackingFile and return a permanent error), and wrap the file so it no longer implements Name() as belt-and-braces. Test: build a qcow2 with a backing file pointing at a temp file, assert Ensure refuses permanently. Related: 4636c0b9 (image_url validation / download cap).


Migrated from git-collab issue 08f40f157abb587838edae47b7002fb3bcdde7d4.

Original author: a73x dev@a73x.sh.

Close reason: merged in patch 3170f758

Linked commit: 48cfc83898cf237a7ca541948e17cabbc6c37432

Linked commit: 81b4b6c1ca2b07794222ef7cd452e02d7dec46b4

[claude 2026-09-02] Found by whole-tree security audit. `internal/agent/imagecache/imagecache.go:345` hands go-qcow2reader an `*os.File`, which satisfies its `Namer` interface. The library honours the qcow2 `backing_file` header: absolute paths are opened verbatim, relative ones (incl. `../..`) resolve against the download dir, via `os.Open` as root. `convert.Convert` (line 394) reads every unallocated cluster through the backing image and the result becomes the guest root disk. Attack: any principal in the tenant (PAT, session, MCP client) crafts a qcow2 whose backing file is `/var/lib/eitri-agent/identity.json` (host credential), `/etc/shadow`, another VM's disk or seed under the state dir, hosts it anywhere, POSTs a VM with its sha, boots it, reads the host file out of its own disk. `image_sha256` is tenant-supplied so it is no barrier. Nothing in the tree references backing files. Scope: hosts are single-tenant, so this is API-principal → host-root read within one tenant. Matters because the design deliberately keeps host material (host credential, guest host keys) off the API, and a short-lived PAT converts into a permanent host credential. Severity Medium. Fix: refuse images that declare a backing file — fail closed at openImage (check the parsed qcow2 header's BackingFileOffset / BackingFile and return a permanent error), and wrap the file so it no longer implements Name() as belt-and-braces. Test: build a qcow2 with a backing file pointing at a temp file, assert Ensure refuses permanently. Related: 4636c0b9 (image_url validation / download cap). --- Migrated from git-collab issue `08f40f157abb587838edae47b7002fb3bcdde7d4`. Original author: a73x <dev@a73x.sh>. Close reason: merged in patch 3170f758 Linked commit: `48cfc83898cf237a7ca541948e17cabbc6c37432` Linked commit: `81b4b6c1ca2b07794222ef7cd452e02d7dec46b4`
a73x commented 2026-09-02 04:39:06 +00:00 (Migrated from git.a73x.sh)

[claude 2026-09-02] Picking this up; branch audit-fixes, one commit per issue, boot-gate before merge.

[claude 2026-09-02] Picking this up; branch audit-fixes, one commit per issue, boot-gate before merge.
a73x commented 2026-09-02 04:57:22 +00:00 (Migrated from git.a73x.sh)

[claude 2026-09-02] Boot gate PASS on branch tip ce66c17 (SMOKE COMPLETE incl. gate SSH, exposed port, MCP, UDP); make ci green incl. vuln after x/crypto v0.55.0. Ready for the human's squash+merge decision.

[claude 2026-09-02] Boot gate PASS on branch tip ce66c17 (SMOKE COMPLETE incl. gate SSH, exposed port, MCP, UDP); make ci green incl. vuln after x/crypto v0.55.0. Ready for the human's squash+merge decision.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
a73x/eitri#109
No description provided.