Skip to content

Incomplete fix of CVE-2026-48752: template-traversal guard added to the LXC driver but not the QEMU/VM driver (arbitrary host file read + root-owned .out file create)

High
tomponline published GHSA-9hcm-hxh5-7xxh Jul 31, 2026

Package

lxd

Affected versions

>= 4.0.0, <= 5.0.7

Patched versions

4.0.12, >= 5.0.7

Description

Reported from GHSA-4qxq-p5hm-3q3p

Summary

The fix for CVE-2026-48752 (GHSA-vxp5-584q-c479, template-traversal to host read/write, addressed in 7.2.0) was applied to the LXC container driver only. The sibling QEMU/VM driver's template path (driver_qemu.gotemplateApplyNow) never received the same guard, so its template read and write remain unbounded.

An authenticated user who can create/launch a VM from a custom image, or restore a VM backup, controls the image/backup metadata.yaml (the templates map and each tpl.Template value) and can traverse out of the templates directory to:

  • read arbitrary host files as root, which are then disclosed into the guest, and
  • create root-owned files anywhere on the host (with a .out suffix).

Details

The LXC driver was hardened for CVE-2026-48752 (internal/server/instance/drivers/driver_lxc.go):

os.OpenRoot(d.RootfsPath())                 // ~:7493  confines writes
if filepath.Base(tpl.Template) != tpl.Template  // ~:7515  rejects any "/", blocks all "../"
os.Lstat(d.TemplatesPath()) ... ModeSymlink  // ~:7519 / ~:7533  rejects symlinked dir/source

The QEMU/VM driver received none of these. driver_qemu.gotemplateApplyNow (func @ :3712) has, before its file operations, only a slices.Contains(tpl.When, trigger) check. There is no path validation:

// READ  ~:3779  -- raw tpl.Template, no containment
tplString, err := os.ReadFile(filepath.Join(d.TemplatesPath(), tpl.Template))

// WRITE ~:3765  -- raw tpl.Template, path is host-side under incusd (root)
w, err = os.Create(filepath.Join(path, fmt.Sprintf("%s.out", tpl.Template)))
  • READ vector: tpl.Template = "../../../../etc/shadow" reads an arbitrary host file, which is rendered into the VM config drive (config/files/*.out, exported to the guest over 9p/virtiofs) → host-file disclosure to the attacker's guest. Cleanest, exact-path vector.
  • WRITE vector: tpl.Template = "../../../../etc/cron.d/x" creates /etc/cron.d/x.out, a root-owned file anywhere on the host with pongo2-rendered content.

A grep for os.OpenRoot / OpenTemplates over the templates directory in the incus QEMU path returns zero hits. Reachability: templateApplyNow is invoked from generateConfigShare on VM start, so the static call path is strong.

Reference remediation. Apply incus's own LXC-driver guards (the filepath.Base reject + os.OpenRoot confinement) to driver_qemu.gotemplateApplyNow, covering both the read and the write. For the read specifically, upstream LXD is a second reference (it confines the QEMU read via d.OpenTemplates(), an os.Root). Note that LXD does not confine the QEMU write either, so the write reference is incus's own LXC guard, not LXD.

Scope / honesty. Source-confirmed via a white-box review of the public repo at fresh HEAD (ccdf811) plus an LXD cross-reference; the guard absence was grep-verified. A live-VM end-to-end PoC is the documented next step. The read is a clean arbitrary host-file disclosure. The write is a genuine root-owned arbitrary-file-create primitive, but the created path is forced to carry a .out suffix, which constrains a direct write-to-RCE, so this is framed as High, not a 9.9 Critical, and no RCE is claimed without a .out-compatible execution path.

PoC

On a self-hosted incus 7.2.0 or later, craft a VM image whose metadata.yaml contains:

templates:
  "/x":
    template: "../../../../../../tmp/incus-poc"
    when:
      - start

Launch the VM, then confirm on the host:

ls -l /tmp/incus-poc.out    # created by incusd (root) = out-of-templates write

Separately, a tpl.Template of ../../etc/hostname is read and disclosed into the guest config drive (config/files/*.out).

Impact

Path traversal (CWE-22) + link following (CWE-59) in the VM/QEMU template path. An authenticated user able to create/launch a VM from a custom image, or restore a VM backup, can:

  1. read arbitrary host files as root, disclosed into their guest (e.g. /etc/shadow), and
  2. create root-owned files with attacker-controlled content anywhere on the host (.out-suffixed).

Undisclosed. The authoritative public dissection (Snyk Labs: Newlines, Symlinks, and Arbitrary Writes: Dissecting High-Severity Vulnerabilities in Incus) covers only driver_lxc.go and explicitly notes the VM drivers were not separately audited; no incus issue, PR, or CVE names driver_qemu.gotemplateApplyNow.


Reporter: Kingsley Olukanni (GitHub: babakizo420).

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N

CVE ID

CVE-2026-16033

Weaknesses

No CWEs

Credits