Skip to content

Incus Backup Import: ../ in a Volume Name, Root on the Host (CVE-2026-115375)

CVE-2026-115375: Incus Backup Path Traversal to Host Root

Incus Backup Import: ../ in a Volume Name, Root on the Host (CVE-2026-115375)

your multi-tenant container host trusted the filename in a backup. bold. Incus before 7.5 validated the instance name on import, then took dependent custom volume names from index.yaml and fed them to filepath.Join like they were housebroken. a ../ chain later, the root daemon is writing attacker content outside the storage pool. CVE-2026-115375. GHSA-hpjh-q53p-f27r. fixed in 7.5.0 / 7.5.1. eleven security fixes in that release. this is the one that makes tenants into landlords.

Lab only. screenshots are a recreation of the vulnerable path (crafted backup + Join collapse + marker write outside a fake pool). we are not running a pre-7.5 Incus daemon on this box. do not import mystery backups into production clusters “to see what happens.” authorized lab only.

what landed

  • CVE-2026-115375 / GHSA-hpjh-q53p-f27r: path traversal in instance backup dependent custom volume names.
  • Related: CVE-2026-115377 (symlink in migration/rsync stream), CVE-2026-115378 and friends in the same 7.5 security bundle. same release train, different shapes of “root writes where it should not.”
  • Affected: Incus before 7.5.0 (NVD notes 7.0.0 before 7.0.2 and 7.1.0 before 7.5.0 for this specific CVE).
  • Patched: 7.5.0 / 7.5.1 (7.5.0 had artifact issues; download 7.5.1).
  • CVSS: advisory 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H). NVD ~9.4.
  • CWE: CWE-22 / CWE-23 (path traversal).

the bug in one paragraph

fixes for earlier backup-name CVEs taught Incus to validate instance names and direct custom-volume import names. the dependent-volume list nested inside an instance backup did not go through those checks. names from backup/index.yaml became path segments under the storage pool. filepath.Join collapses ../. os.Mkdir and the volume unpack then run as the Incus daemon. the daemon is root. you see where this is going.

Kali terminal extracting backup/index.yaml from a crafted Incus instance backup showing a dependent volume name with ../../../outside-pool/pwned-vol.
Step 1: index.yaml. instance name looks fine. the volume name is the knife.

step 1: craft the backup

an instance backup tarball carries backup/index.yaml plus rootfs and any dependent custom volumes. the advisory path:

volumes:
  - name: ../../../outside-pool/pwned-vol
    type: custom
    pool: default
    project: default

on a real host the traversal aims at something useful: /etc/cron.d/, a systemd drop-in, an authorized_keys path, a web root. in lab we point it at a sibling directory outside the fake pool so the screenshot is honest about “outside” without pretending we rooted a cluster.

bonus cruelty from the advisory: the backup also names the project each dependent volume is created in, so the same request can create volumes in projects the caller should not touch.

Kali terminal showing filepath.Join of the storage pool path with the traversal volume name resolving outside the pool, then writing sloth-incus.marker.
Step 2: Join collapses ../. outside storage pool: True. root-shaped write follows.

step 2: Join is not a security boundary

pool path looks like .../storage-pools/default/custom. volume name ../../../outside-pool/pwned-vol joins into a path that leaves that tree. no IsAPIName, no clean-and-contain check on this branch in the affected builds. the attached-device gate can be satisfied by splitting the traversal across device pool and source fields. once the directory exists, volume contents unpack into it.

filepath.Join(pool, "../../../outside-pool/pwned-vol")
# => .../outside-pool/pwned-vol   (outside the pool)
Kali terminal find/cat showing the marker file under outside-pool/pwned-vol while the fake storage pool custom dir stayed empty.
Step 3: marker lives outside the pool. pool custom dir stayed empty. that is the demo. on a host it is persistence.

step 3: impact

any client allowed to create instances in a project can make the daemon create root-owned directories and drop attacker-controlled content at arbitrary host paths. that is full host compromise from a “backup import” button. multi-tenant Incus with untrusted project users is the nightmare configuration. single-admin homelab with no remote create rights is a much smaller fish, but still patch it before you invite friends.

why defenders care

  • incomplete fix residue: earlier name-validation CVEs left a nested path unchecked. patch one door, check the hallway.
  • root daemon: Incus has to manage storage. that privilege is why a path bug becomes host RCE instead of a sad 403.
  • tenant to host: “project isolation” is a product claim. this bug turns it into a slide deck.
  • 7.5 ships a pile: eleven security fixes. if you are still on 7.2 because “it was fine,” update your definition of fine.

controls that are not theater

  1. upgrade to Incus 7.5.1 (or current) on every cluster node. staging first if you must, but do not schedule this for next quarter.
  2. only trusted principals get can_create_instances / backup import rights. “every developer in the org” is not a threat model, it is a wish.
  3. monitor for Incus creating paths outside configured storage pool roots (auditd, fanotify, IMA, take your pick).
  4. treat backup import as code execution: scan, stage, review. especially backups that did not originate on your infrastructure.
  5. network-isolate the Incus API. project users should not be poking the daemon from the coffee shop VPN without MFA and a ticket.
honesty check: screenshots recreate the vulnerable Join/unpack path with a crafted tarball and a fake pool. a live pre-7.5 daemon import looks the same and ends with root-owned files wherever the traversal aimed. upgrade instead of collecting that screenshot in production.

sloth take

you validated the front door name and left the volume names in the moving boxes unchecked. the boxes had ../ in the address. the root daemon delivered them anyway. upgrade to 7.5.1. stop letting untrusted clients create instances. and maybe, just maybe, stop treating backup import as a boring file copy.

sources